Compare commits
39 Commits
f3aaec99bb
...
v0.11.0
| Author | SHA1 | Date | |
|---|---|---|---|
| 1f9d618777 | |||
| ce11c03326 | |||
| 0ade4798f3 | |||
| 843f579305 | |||
| 7515b9c0e5 | |||
| 7f696582a9 | |||
| 5fea98b068 | |||
| 0b5098370a | |||
| 599de22f59 | |||
| 23521ad6ae | |||
| 0d276e8589 | |||
| 587d2f5889 | |||
| bf4767d06b | |||
| 75c8f8c325 | |||
| 6852ad915e | |||
| c4ee5cc645 | |||
| 061ba88206 | |||
| 4e9117ddcb | |||
| 5e3646fdff | |||
| fc828a345b | |||
| 4d72ee8032 | |||
| 689fb21774 | |||
| 20c0cfece7 | |||
| 0d78837a53 | |||
| 2836ae14de | |||
| 5264a6083c | |||
| a969c5de47 | |||
| f27eea6b5b | |||
| ae2e8ee4a7 | |||
| b10d2fd3d0 | |||
| 92719ff424 | |||
| 9026646594 | |||
| 77415bfad7 | |||
| 5e147865f8 | |||
| 3003b9b8da | |||
| d32dc41315 | |||
| f19a88f1d5 | |||
| 7d115b6325 | |||
| 60d9f7a2c3 |
82
CHANGELOG.md
82
CHANGELOG.md
@@ -5,43 +5,105 @@ All notable changes to this project will be documented in this file.
|
|||||||
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
|
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
|
||||||
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
|
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
|
||||||
|
|
||||||
|
See roadmap/YYMMDD-ROADMAPTOPIC/ directories for planning information like concepts, workplans, etc...
|
||||||
|
See history/YYMMDD-ROADMAOTOPIC/ directories for planning information of closed topics
|
||||||
|
|
||||||
## [Unreleased]
|
## [Unreleased]
|
||||||
|
|
||||||
|
## [0.11.0] - 2026-01-06
|
||||||
|
|
||||||
|
### Added
|
||||||
|
- Release management optimizations: CHANGELOG validation, version-tag consistency checks
|
||||||
|
- Automated tag pushing with --push/--no-push flag
|
||||||
|
- Unpushed tags detection in release status
|
||||||
|
|
||||||
|
### Changed
|
||||||
|
- Improved release validation workflow with CHANGELOG schema validation
|
||||||
|
|
||||||
|
|
||||||
|
## [0.10.0] - 2026-01-06
|
||||||
|
|
||||||
### Added
|
### Added
|
||||||
- **Schema Management System**: Comprehensive schema management infrastructure with naming conventions and versioning
|
- **Schema Management System**: Comprehensive schema management infrastructure with naming conventions and versioning
|
||||||
- Naming convention: `{domain}-schema-v{major}.{minor}.md` for all schemas
|
- Naming convention: `{domain}-schema-v{major}.{minor}.md` for all schemas
|
||||||
- Markdown-first schema format with embedded JSON (documentation + schema in one file)
|
- Markdown-first schema format with embedded JSON (documentation + schema in one file)
|
||||||
- Schema catalog (`markitect/schemas/schema-catalog.yaml`) for metadata and discovery
|
- Schema catalog (`markitect/schemas/schema-catalog.yaml`) for metadata and discovery
|
||||||
- Terminology validation example (`examples/terminology/`) demonstrating schema usage beyond manpages
|
- Terminology validation example (`examples/terminology/`) demonstrating schema usage beyond manpages
|
||||||
- Schema-for-schemas workplan in `roadmap/schema-of-schemas/` directory
|
- Schema-of-schemas implementation archived in `history/2026-01-05-schema-of-schemas/`
|
||||||
- **Enhanced schema-list Command**: Now displays creation timestamps in all output formats
|
- **Enhanced schema-list Command**: Now displays numbered references in all output formats for easy selection
|
||||||
|
- Simple format: `[1] schema-name.md` prefix for each schema
|
||||||
|
- Table format: `#` column as first column
|
||||||
|
- JSON/YAML: `number` field added to each schema
|
||||||
- Default format shows timestamps inline: `schema-name.json (added: 2026-01-04T23:01:19)`
|
- Default format shows timestamps inline: `schema-name.json (added: 2026-01-04T23:01:19)`
|
||||||
- Table format includes Created/Updated columns
|
- Table format includes Created/Updated columns
|
||||||
- Cleaner timestamp formatting (removed microseconds)
|
- Cleaner timestamp formatting (removed microseconds)
|
||||||
|
- **Multi-Schema Validation**: Enhanced schema-validate command with multiple selection methods
|
||||||
|
- Number selection: `markitect schema-validate 1` validates schema #1
|
||||||
|
- Range selection: `markitect schema-validate 1-3` validates schemas #1-3
|
||||||
|
- List selection: `markitect schema-validate 1,3,5` validates schemas #1,3,5
|
||||||
|
- Batch validation: `markitect schema-validate --all` validates all registered schemas
|
||||||
|
- Filename selection: `markitect schema-validate schema.md` from registry
|
||||||
|
- Filesystem path: `markitect schema-validate ./schema.md` from disk
|
||||||
|
- Batch results displayed as clear summary table with validation status
|
||||||
|
- Registry schemas take precedence over filesystem (with fallback)
|
||||||
|
- Full backward compatibility with existing single-file validation
|
||||||
- Enhanced control panel UI with better resize handle positioning for improved user interaction
|
- Enhanced control panel UI with better resize handle positioning for improved user interaction
|
||||||
|
- **Semantic Document Validation**: Complete semantic validation system for markdown documents against x-markitect schema extensions
|
||||||
|
- Section classification enforcement: required/recommended/optional/discouraged/improper sections validated
|
||||||
|
- Content pattern validation: required_patterns, forbidden_patterns, discouraged_patterns with regex matching
|
||||||
|
- Quality metrics checking: min_words, max_words, min_sentences validation with configurable thresholds
|
||||||
|
- Link validation: Internal/external link checking with configurable policies
|
||||||
|
- Internal links: Fragment anchors (#section) and file paths validated by default
|
||||||
|
- External links: HTTP/HTTPS validation with --check-links flag (opt-in, may be slow)
|
||||||
|
- Email validation: mailto: link format checking
|
||||||
|
- Broken link detection with line numbers and detailed error messages
|
||||||
|
- Modular validator architecture: SectionValidator, ContentValidator, LinkValidator with clean separation of concerns
|
||||||
|
- CLI integration: `--semantic/--no-semantic`, `--strict`, `--check-links` flags for validate command
|
||||||
|
- Comprehensive reporting: Detailed validation reports with errors/warnings, line numbers, matched text
|
||||||
|
- Test coverage: 25 tests for semantic validators (16 section/content + 9 link), 100% passing
|
||||||
|
- Full documentation: Semantic validation guide in SCHEMA_MANAGEMENT_GUIDE.md with examples
|
||||||
|
- Complements existing structural AST validation for complete document compliance checking
|
||||||
|
- **Changelog Schema**: Production schema for validating CHANGELOG.md files following Keep a Changelog format
|
||||||
|
- Schema file: `changelog-schema-v1.0.md` validates version history structure and formatting
|
||||||
|
- Enforces Unreleased section presence (required)
|
||||||
|
- Validates version format: `[X.Y.Z] - YYYY-MM-DD` with semantic versioning
|
||||||
|
- Validates change type subsections: Added, Changed, Deprecated, Removed, Fixed, Security
|
||||||
|
- Content pattern validation for version sections, date formats (ISO 8601), and change types
|
||||||
|
- Demonstrates real-world schema system usage: "The release that validates itself"
|
||||||
|
- Successfully validates project CHANGELOG.md with all semantic checks passing
|
||||||
|
|
||||||
### Changed
|
### Changed
|
||||||
- **Directory Reorganization**: Renamed `todo/` → `roadmap/` for better organization of planning documents
|
- **Directory Reorganization**:
|
||||||
- Created `roadmap/schema-of-schemas/` subdirectory for schema management planning artifacts
|
- Renamed `todo/` → `roadmap/` for better organization of planning documents
|
||||||
- Moved schema management proposals and workplan to dedicated directory
|
- Completed schema-of-schemas implementation archived to `history/2026-01-05-schema-of-schemas/`
|
||||||
|
- Moved completed planning artifacts to history for reference
|
||||||
- Refactored contents control architecture to use base class pattern properly for better code organization
|
- Refactored contents control architecture to use base class pattern properly for better code organization
|
||||||
- Updated all file references and paths to point to single source of truth in capabilities/testdrive-jsui/js/controls/ directory
|
- Updated all file references and paths to point to single source of truth in capabilities/testdrive-jsui/js/controls/ directory
|
||||||
|
|
||||||
### Fixed
|
### Fixed
|
||||||
|
- **Version Detection Issue**: Fixed `markitect --version` returning "unknown" instead of actual version
|
||||||
|
- Added `git_describe_command` to setuptools-scm configuration to filter version tags correctly
|
||||||
|
- Configured git describe to use `--match 'v*'` pattern to ignore non-version tags
|
||||||
|
- Version detection now works correctly with development versions (e.g., 0.9.1.dev76)
|
||||||
|
- **Missing v0.9.0 Git Tag**: Retroactively created v0.9.0 annotated tag on commit b9c1b90 from 2025-11-14
|
||||||
|
- Maintains version history integrity (CHANGELOG documented v0.9.0 but tag was missing)
|
||||||
|
- Enables proper version progression to v0.10.0
|
||||||
- Duplicate file structure issue by eliminating duplicate control files and consolidating to capabilities/ directory
|
- Duplicate file structure issue by eliminating duplicate control files and consolidating to capabilities/ directory
|
||||||
- Contents panel scrollbar behavior - moved overflow-y: auto to correct container level so scrollbar only spans content area when panel reaches max-height
|
- Contents panel scrollbar behavior - moved overflow-y: auto to correct container level so scrollbar only spans content area when panel reaches max-height
|
||||||
|
|
||||||
### Removed
|
### Removed
|
||||||
- **BREAKING**: Legacy DocumentControls component from TestDrive JSUI plugin system - all control panel functionality now provided by enhanced control panels (ContentsControl, StatusControl, DebugControl, EditControl) with Reset All button functionality moved to EditControl for better maintainability and elimination of code duplication
|
- **BREAKING**: Legacy DocumentControls component from TestDrive JSUI plugin system - all control panel functionality now provided by enhanced control panels (ContentsControl, StatusControl, DebugControl, EditControl) with Reset All button functionality moved to EditControl for better maintainability and elimination of code duplication
|
||||||
|
|
||||||
### In Progress
|
### Completed Features
|
||||||
- **Schema-of-Schemas Implementation** (Phase 3 of 6 - Completed ✅)
|
- **Schema-of-Schemas Implementation** (All 6 Phases Complete ✅)
|
||||||
- ✅ Phase 1: Filename validation for schema naming convention (`markitect/schema_naming.py`, 50 tests)
|
- ✅ Phase 1: Filename validation for schema naming convention (`markitect/schema_naming.py`, 50 tests)
|
||||||
- ✅ Phase 2: Markdown schema loader to parse `.md` schema files (`markitect/schema_loader.py`, 35 tests)
|
- ✅ Phase 2: Markdown schema loader to parse `.md` schema files (`markitect/schema_loader.py`, 35 tests)
|
||||||
- ✅ Phase 3: Schema-for-schemas metaschema for schema validation (`schema-schema-v1.0.md`, 12 tests)
|
- ✅ Phase 3: Schema-for-schemas metaschema for schema validation (`schema-schema-v1.0.md`, 12 tests)
|
||||||
- ⏳ Phase 4: Migration of 5 existing schemas to new format (will remove 2 duplicates)
|
- ✅ Phase 4: Migration of 5 existing schemas to new format (migrated 2, deleted 3 duplicates)
|
||||||
- ⏳ Phase 5: CLI updates and documentation
|
- ✅ Phase 5: CLI enhancements - numbered schema-list, multi-schema validation with selection methods
|
||||||
- ⏳ Phase 6: Integration testing and validation
|
- ✅ Phase 6: Integration testing and comprehensive documentation (SCHEMA_MANAGEMENT_GUIDE.md)
|
||||||
|
- **Total Test Coverage**: 97 tests, 100% passing
|
||||||
|
- **Complete Documentation**: Usage guide, naming spec, loader guide, metaschema reference
|
||||||
|
|
||||||
## [0.9.0] - 2025-11-14
|
## [0.9.0] - 2025-11-14
|
||||||
|
|
||||||
|
|||||||
149
TODO.html
149
TODO.html
@@ -1,149 +0,0 @@
|
|||||||
<!DOCTYPE html>
|
|
||||||
<html lang="en">
|
|
||||||
<head>
|
|
||||||
<meta charset="utf-8">
|
|
||||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
|
||||||
<meta name="generator" content="TestDrive JSUI Markitect 1.0.0">
|
|
||||||
<title>TestDrive JSUI Document</title>
|
|
||||||
|
|
||||||
<style>
|
|
||||||
body {
|
|
||||||
font-family: -apple-system, BlinkMacSystemFont, Segoe UI, Helvetica, Arial, sans-serif;
|
|
||||||
max-width: 800px;
|
|
||||||
margin: 0 auto;
|
|
||||||
padding: 2rem;
|
|
||||||
line-height: 1.6;
|
|
||||||
color: #333333;
|
|
||||||
background-color: #ffffff;
|
|
||||||
}
|
|
||||||
#markdown-content {
|
|
||||||
min-height: 200px;
|
|
||||||
}
|
|
||||||
h1, h2, h3, h4, h5, h6 {
|
|
||||||
color: #333333;
|
|
||||||
}
|
|
||||||
pre {
|
|
||||||
background-color: #f6f8fa;
|
|
||||||
color: #333333;
|
|
||||||
padding: 1rem;
|
|
||||||
border-radius: 6px;
|
|
||||||
overflow-x: auto;
|
|
||||||
border: 1px solid #d0d7de;
|
|
||||||
}
|
|
||||||
code {
|
|
||||||
background-color: #f6f8fa;
|
|
||||||
color: #333333;
|
|
||||||
padding: 0.2em 0.4em;
|
|
||||||
border-radius: 3px;
|
|
||||||
font-size: 0.9em;
|
|
||||||
}
|
|
||||||
pre code {
|
|
||||||
background: none;
|
|
||||||
padding: 0;
|
|
||||||
}
|
|
||||||
blockquote {
|
|
||||||
border-left: 4px solid #dfe2e5;
|
|
||||||
margin: 0;
|
|
||||||
padding-left: 1rem;
|
|
||||||
color: #6a737d;
|
|
||||||
}
|
|
||||||
table {
|
|
||||||
font-size: 0.85em;
|
|
||||||
border-collapse: collapse;
|
|
||||||
margin: 1rem 0;
|
|
||||||
width: 100%;
|
|
||||||
border: 1px solid #d0d7de;
|
|
||||||
}
|
|
||||||
th, td {
|
|
||||||
font-size: inherit;
|
|
||||||
border: 1px solid #d0d7de;
|
|
||||||
padding: 0.5rem;
|
|
||||||
text-align: left;
|
|
||||||
}
|
|
||||||
th {
|
|
||||||
background-color: #f6f8fa;
|
|
||||||
font-weight: 600;
|
|
||||||
}
|
|
||||||
img {
|
|
||||||
max-width: 12cm;
|
|
||||||
max-height: 20cm;
|
|
||||||
height: auto;
|
|
||||||
display: block;
|
|
||||||
margin: 1rem auto;
|
|
||||||
}
|
|
||||||
</style>
|
|
||||||
|
|
||||||
<!-- Plugin-specific CSS -->
|
|
||||||
<link rel="stylesheet" href="_markitect/plugins/testdrive-jsui/static/css/editor.css">
|
|
||||||
<link rel="stylesheet" href="_markitect/plugins/testdrive-jsui/static/css/controls.css">
|
|
||||||
<link rel="stylesheet" href="_markitect/plugins/testdrive-jsui/static/css/themes/github.css">
|
|
||||||
|
|
||||||
<!-- External dependencies -->
|
|
||||||
<script src="https://cdn.jsdelivr.net/npm/marked/marked.min.js"
|
|
||||||
onload="window.markitectMarkedLoaded = true"
|
|
||||||
onerror="console.error('CDN library failed to load - network or firewall blocking marked.js'); window.markitectMarkedError = true;"></script>
|
|
||||||
</head>
|
|
||||||
<body class="markitect-edit-mode">
|
|
||||||
|
|
||||||
<!-- Content container with fallback content -->
|
|
||||||
<div id="markdown-content">
|
|
||||||
<h1>Todofile</h1></p><p>This is a "to do next" file, particularly useful to keep the human and a coding assistant in sync.</p><p>The format is based on [Keep a Todofile V0.0.1](https://coulomb.social/open/KeepaTodofile).</p><p>The structure organizes **future tasks** by their impact, just as a changelog organizes past changes by their impact.</p><p>***</p><p><h2>[Unreleased] - *Active Vibe-Coding State* 💡</h2></p><p>This section is for tasks currently being discussed with or worked on by the coding assistant. These are the ephemeral, flow-of-thought tasks.</p><p>*No active tasks at this time.*</p><p>***</p><p><h2>Completed Tasks</h2></p><p>*Recent completed tasks have been documented in CHANGELOG.md following Keep a Changelog format.*
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<!-- Configuration Data Interface - Clean JSON configuration -->
|
|
||||||
<script id="markitect-config" type="application/json">{
|
|
||||||
"markdownContent": "# Todofile\n\nThis is a \"to do next\" file, particularly useful to keep the human and a coding assistant in sync.\n\nThe format is based on [Keep a Todofile V0.0.1](https://coulomb.social/open/KeepaTodofile).\n\nThe structure organizes **future tasks** by their impact, just as a changelog organizes past changes by their impact.\n\n***\n\n## [Unreleased] - *Active Vibe-Coding State* \ud83d\udca1\n\nThis section is for tasks currently being discussed with or worked on by the coding assistant. These are the ephemeral, flow-of-thought tasks.\n\n*No active tasks at this time.*\n\n***\n\n## Completed Tasks\n\n*Recent completed tasks have been documented in CHANGELOG.md following Keep a Changelog format.*",
|
|
||||||
"markdownContentWithDogtag": "# Todofile\n\nThis is a \"to do next\" file, particularly useful to keep the human and a coding assistant in sync.\n\nThe format is based on [Keep a Todofile V0.0.1](https://coulomb.social/open/KeepaTodofile).\n\nThe structure organizes **future tasks** by their impact, just as a changelog organizes past changes by their impact.\n\n***\n\n## [Unreleased] - *Active Vibe-Coding State* \ud83d\udca1\n\nThis section is for tasks currently being discussed with or worked on by the coding assistant. These are the ephemeral, flow-of-thought tasks.\n\n*No active tasks at this time.*\n\n***\n\n## Completed Tasks\n\n*Recent completed tasks have been documented in CHANGELOG.md following Keep a Changelog format.*",
|
|
||||||
"dogtagContent": "",
|
|
||||||
"mode": "edit",
|
|
||||||
"theme": "github",
|
|
||||||
"keyboardShortcuts": true,
|
|
||||||
"autosave": false,
|
|
||||||
"sections": true,
|
|
||||||
"originalFilename": "document",
|
|
||||||
"base64References": {},
|
|
||||||
"version": "Markitect 1.0.0",
|
|
||||||
"repoName": "Markitect"
|
|
||||||
}</script>
|
|
||||||
|
|
||||||
<!-- Plugin JavaScript Assets -->
|
|
||||||
<script src="https://cdn.jsdelivr.net/npm/marked/marked.min.js"></script>
|
|
||||||
<script src="_markitect/plugins/testdrive-jsui/static/js/core/debug-system.js"></script>
|
|
||||||
<script src="_markitect/plugins/testdrive-jsui/static/js/core/section-manager.js"></script>
|
|
||||||
<script src="_markitect/plugins/testdrive-jsui/static/js/components/debug-panel.js"></script>
|
|
||||||
<script src="_markitect/plugins/testdrive-jsui/static/js/components/document-controls.js"></script>
|
|
||||||
<script src="_markitect/plugins/testdrive-jsui/static/js/components/dom-renderer.js"></script>
|
|
||||||
<script src="_markitect/plugins/testdrive-jsui/static/js/controls/control-base.js"></script>
|
|
||||||
<script src="_markitect/plugins/testdrive-jsui/static/js/controls/contents-control.js"></script>
|
|
||||||
<script src="_markitect/plugins/testdrive-jsui/static/js/controls/status-control.js"></script>
|
|
||||||
<script src="_markitect/plugins/testdrive-jsui/static/js/controls/debug-control.js"></script>
|
|
||||||
<script src="_markitect/plugins/testdrive-jsui/static/js/controls/edit-control.js"></script>
|
|
||||||
<script src="_markitect/plugins/testdrive-jsui/static/js/config-loader.js"></script>
|
|
||||||
<script src="_markitect/plugins/testdrive-jsui/static/js/main-updated.js"></script>
|
|
||||||
|
|
||||||
<!-- Initialization Script -->
|
|
||||||
<script>
|
|
||||||
window.addEventListener('load', function() {
|
|
||||||
console.log('🎯 TestDrive JSUI loading complete, initializing...');
|
|
||||||
|
|
||||||
// Handle CDN loading errors
|
|
||||||
if (window.markitectMarkedError) {
|
|
||||||
console.error("CDN library failed to load - network or firewall blocking marked.js");
|
|
||||||
}
|
|
||||||
|
|
||||||
// Initialize main application
|
|
||||||
try {
|
|
||||||
if (typeof MarkitectMain !== 'undefined') {
|
|
||||||
console.log('🚀 Starting MarkitectMain initialization...');
|
|
||||||
MarkitectMain.initialize();
|
|
||||||
} else {
|
|
||||||
console.warn('⚠️ MarkitectMain not available, edit functionality may be limited');
|
|
||||||
}
|
|
||||||
} catch (error) {
|
|
||||||
console.error('❌ TestDrive JSUI initialization failed:', error);
|
|
||||||
console.log('📄 Content should still be visible in fallback mode');
|
|
||||||
}
|
|
||||||
});
|
|
||||||
</script>
|
|
||||||
</body>
|
|
||||||
</html>
|
|
||||||
136
TODO.md
136
TODO.md
@@ -6,56 +6,14 @@ The format is based on [Keep a Todofile V0.0.1](https://coulomb.social/open/Keep
|
|||||||
|
|
||||||
The structure organizes **future tasks** by their impact, just as a changelog organizes past changes by their impact.
|
The structure organizes **future tasks** by their impact, just as a changelog organizes past changes by their impact.
|
||||||
|
|
||||||
|
See roadmap/YYMMDD-ROADMAPTOPIC/ directories for planning information like concepts, workplans, etc...
|
||||||
|
|
||||||
***
|
***
|
||||||
|
|
||||||
## [Unreleased] - *Active Vibe-Coding State* 💡
|
## [Unreleased] - *Active Vibe-Coding State* 💡
|
||||||
|
|
||||||
This section is for tasks currently being discussed with or worked on by the coding assistant. These are the ephemeral, flow-of-thought tasks.
|
This section is for tasks currently being discussed with or worked on by the coding assistant. These are the ephemeral, flow-of-thought tasks.
|
||||||
|
|
||||||
### Schema-of-Schemas Implementation (Active - Phase 3)
|
|
||||||
|
|
||||||
**Status:** Phase 3 - Schema-for-Schemas Metaschema (Completed ✅)
|
|
||||||
**Workplan:** See `roadmap/schema-of-schemas/WORKPLAN.md`
|
|
||||||
|
|
||||||
**Current Goals:**
|
|
||||||
1. ✅ Establish naming convention: `{domain}-schema-v{major}.{minor}.md`
|
|
||||||
2. ✅ Implement filename validation logic
|
|
||||||
3. ✅ Create markdown schema loader
|
|
||||||
4. ✅ Create example markdown schema
|
|
||||||
5. ✅ Build schema-for-schemas metaschema
|
|
||||||
6. ⏳ Migrate existing schemas to new format (Next: Phase 4)
|
|
||||||
|
|
||||||
**Phase 1 Tasks (Completed ✅):**
|
|
||||||
- [x] Write `markitect/schema_naming.py` with validation logic
|
|
||||||
- [x] Add unit tests for filename validation (50 tests, 100% passing)
|
|
||||||
- [x] Create SCHEMA_NAMING_SPEC.md documentation
|
|
||||||
|
|
||||||
**Phase 2 Tasks (Completed ✅):**
|
|
||||||
- [x] Implement MarkdownSchemaLoader class (markitect/schema_loader.py, 515 lines)
|
|
||||||
- [x] Add frontmatter extraction (YAML)
|
|
||||||
- [x] Add JSON code block extraction with section preference
|
|
||||||
- [x] Add metadata merging with x-markitect-source tracking
|
|
||||||
- [x] Write comprehensive unit tests (35 tests, 100% passing)
|
|
||||||
- [x] Create example markdown schema (manpage-schema-v1.0.md)
|
|
||||||
- [x] Create SCHEMA_LOADER_GUIDE.md documentation
|
|
||||||
|
|
||||||
**Phase 3 Tasks (Completed ✅):**
|
|
||||||
- [x] Design schema-for-schemas metaschema (schema-schema-v1.0.md)
|
|
||||||
- [x] Implement metaschema with validation rules for MarkiTect conventions
|
|
||||||
- [x] Add schema-validate CLI command with detailed error reporting
|
|
||||||
- [x] Write comprehensive unit tests (12 tests, 100% passing)
|
|
||||||
- [x] Test metaschema self-validation
|
|
||||||
- [x] Validate existing schemas against metaschema
|
|
||||||
|
|
||||||
**Next Phases:**
|
|
||||||
- Phase 4: Schema Migration (1-2 days)
|
|
||||||
- Phase 5: CLI & Documentation Updates (1 day)
|
|
||||||
- Phase 6: Testing & Validation (1 day)
|
|
||||||
|
|
||||||
**Expected Completion:** 4-5 days remaining
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
### Extract Capability-Capability from Issue-Facade (Paused)
|
### Extract Capability-Capability from Issue-Facade (Paused)
|
||||||
|
|
||||||
**Context:** Issue-facade currently provides two capabilities:
|
**Context:** Issue-facade currently provides two capabilities:
|
||||||
@@ -112,93 +70,3 @@ The **capability-capability** includes:
|
|||||||
|
|
||||||
**Current Step:** Phase 1, Task 1 - Create CAPABILITY-capability.yaml
|
**Current Step:** Phase 1, Task 1 - Create CAPABILITY-capability.yaml
|
||||||
|
|
||||||
***
|
|
||||||
|
|
||||||
## Completed Tasks
|
|
||||||
|
|
||||||
*Recent completed tasks have been documented in _issue-tracking/issue-facade/CHANGELOG.md following Keep a Changelog format.*
|
|
||||||
|
|
||||||
### 2026-01-04 - Phase 2: Schema Refinement Tools & Terminology Example
|
|
||||||
- ✅ Implemented schema-analyze command to detect rigidity issues
|
|
||||||
- ✅ Implemented schema-refine command with automatic loosening logic
|
|
||||||
- ✅ Added interactive mode to schema-refine for fine-grained control
|
|
||||||
- ✅ Created comprehensive test suite (33 unit tests, 100% passing)
|
|
||||||
- ✅ Wrote user guide documentation with examples and workflows
|
|
||||||
- ✅ Successfully tested on example schemas (reduced rigidity from 60/100 to 24/100)
|
|
||||||
- ✅ Integrated into CLI with proper exit codes and error handling
|
|
||||||
- ✅ Moved SCHEMA_EVOLUTION_WORKPLAN.md to todo/ directory
|
|
||||||
- ✅ Created terminology validation example (examples/terminology/)
|
|
||||||
|
|
||||||
**Key Features Delivered:**
|
|
||||||
- Rigidity score calculation (0-100 scale)
|
|
||||||
- Automatic detection of exact counts, const values, overly specific numbers
|
|
||||||
- Path navigation for nested schema properties
|
|
||||||
- Dry-run mode for previewing changes
|
|
||||||
- Interactive approval workflow
|
|
||||||
- Comprehensive reporting (normal and verbose modes)
|
|
||||||
|
|
||||||
**Terminology Example:**
|
|
||||||
- Complete terminology document structure (terminology-example.md)
|
|
||||||
- JSON schema with MarkiTect extensions (terminology-schema.json)
|
|
||||||
- Demonstrates schema usage for non-manpage documents
|
|
||||||
- Validates term definitions, synonyms, related terms, examples
|
|
||||||
- Includes content control and validation rules
|
|
||||||
- Full documentation and usage examples (README.md)
|
|
||||||
|
|
||||||
### 2026-01-04 - Phase 2: Markdown Schema Loader
|
|
||||||
- ✅ Implemented MarkdownSchemaLoader class (markitect/schema_loader.py, 515 lines)
|
|
||||||
- ✅ YAML frontmatter extraction with validation
|
|
||||||
- ✅ JSON code block extraction with "Schema Definition" section preference
|
|
||||||
- ✅ Metadata merging with x-markitect-source tracking
|
|
||||||
- ✅ Schema saving with template support and round-trip capability
|
|
||||||
- ✅ Comprehensive test suite (35 unit tests, 100% passing)
|
|
||||||
- ✅ Created example markdown schema (manpage-schema-v1.0.md)
|
|
||||||
- ✅ Created SCHEMA_LOADER_GUIDE.md with complete usage documentation
|
|
||||||
|
|
||||||
**Key Features Delivered:**
|
|
||||||
- Markdown-first schema format with embedded JSON
|
|
||||||
- Frontmatter metadata merges into schema ($id, version, status)
|
|
||||||
- Automatic detection of multiple JSON blocks
|
|
||||||
- Schema structure validation helper
|
|
||||||
- Error handling for binary files and invalid formats
|
|
||||||
- List JSON blocks helper for debugging
|
|
||||||
- Full round-trip save/load capability
|
|
||||||
|
|
||||||
**Example Markdown Schema:**
|
|
||||||
- manpage-schema-v1.0.md demonstrating complete format
|
|
||||||
- Includes frontmatter, documentation, and JSON schema
|
|
||||||
- Shows section classification and content control
|
|
||||||
- Follows naming convention: {domain}-schema-v{major}.{minor}.md
|
|
||||||
|
|
||||||
### 2026-01-04 - Phase 3: Schema-for-Schemas Metaschema
|
|
||||||
- ✅ Created schema-schema-v1.0.md metaschema (650+ lines)
|
|
||||||
- ✅ Validates core JSON Schema fields ($schema, $id, title, description)
|
|
||||||
- ✅ Validates MarkiTect version field (SemVer: major.minor.patch)
|
|
||||||
- ✅ Validates $id URL format (HTTPS with version)
|
|
||||||
- ✅ Validates MarkiTect extensions (x-markitect-sections, x-markitect-content-control, x-markitect-metadata)
|
|
||||||
- ✅ Implemented schema-validate CLI command with detailed error reporting
|
|
||||||
- ✅ Comprehensive test suite (12 unit tests, 100% passing)
|
|
||||||
- ✅ Metaschema self-validation successful
|
|
||||||
|
|
||||||
**Key Features Delivered:**
|
|
||||||
- Complete metaschema for validating all MarkiTect schemas
|
|
||||||
- Section classification validation (required, recommended, optional, discouraged, improper)
|
|
||||||
- Content control pattern validation
|
|
||||||
- Version format enforcement (SemVer)
|
|
||||||
- $id URL format enforcement (HTTPS with version)
|
|
||||||
- CLI command for easy schema validation
|
|
||||||
- Detailed error messages with schema paths
|
|
||||||
|
|
||||||
**Validation Results:**
|
|
||||||
- ✅ Metaschema validates itself
|
|
||||||
- ✅ Manpage schema validates successfully
|
|
||||||
- ⚠️ Terminology schema needs migration (missing version field, incorrect $id format)
|
|
||||||
|
|
||||||
### 2025-12-17 - Architecture Refactoring
|
|
||||||
- ✅ Implemented ReusableCapabilitiesArchitecture v0.1
|
|
||||||
- ✅ Added feedback capability to issue-facade
|
|
||||||
- ✅ Created detachment facility
|
|
||||||
- ✅ Refactored to family-based directory structure (_issue-tracking/issue-facade)
|
|
||||||
- ✅ Made feedback directory visible (feedback/ not .feedback/)
|
|
||||||
- ✅ Renamed to explicit family declaration (CAPABILITY-issue-tracking.yaml)
|
|
||||||
- ✅ Created CHANGELOG.md documenting v1.0.0
|
|
||||||
|
|||||||
@@ -15,19 +15,25 @@ You are the MarkiTect project assistant, specialized in providing project status
|
|||||||
|
|
||||||
### Key Project Files & Their Purpose
|
### Key Project Files & Their Purpose
|
||||||
|
|
||||||
- **ProjectStatusDigest.md**: The canonical source of truth for project architecture, features, and current state
|
- **TODO.md**: Current state of implemenation based on the Keep-A-Todofile format for maintaining coding flow
|
||||||
- **ProjectDiary.md**: Chronological record of major work packages, milestones, and development sessions
|
- **CHANGELOG.md**: History of releases based on the Keep-A-Changelog format for easy access to what happend before
|
||||||
- **TODO.md**: Task management and priorities following Keep a Todofile format for maintaining coding flow
|
- **roadmap/**: Directory with current and close range roadmap-topic-directories for concepts, workplans, examples...
|
||||||
|
- **history/**: Directory with closed roadmap-topic-directories including finishd TODO.md files as YYMMDD-DONE.md
|
||||||
- **Makefile**: Provides helpers to use and improve the capabilities provided by the project
|
- **Makefile**: Provides helpers to use and improve the capabilities provided by the project
|
||||||
**Gitea Issues**: Backlog of issues and backlog of tasks stored as issues in gitea
|
**Gitea Issues**: Backlog of issues and backlog of tasks stored as issues in gitea before selection as roadmap topics
|
||||||
|
|
||||||
### Project Infrastructure Knowledge
|
### Project Infrastructure Knowledge
|
||||||
|
|
||||||
**Repository Structure:**
|
**Repository Structure:**
|
||||||
- Main project hosted on Gitea with issue tracking for use cases and tasks
|
- Main project hosted on Gitea with issue tracking for use cases and tasks
|
||||||
- Documentation maintained in `wiki/` submodule
|
- Planning documentation goes to roadmap/ROADMAPTOPIC subdirectories
|
||||||
|
- Closed roadmap-topic-directories git-mv to history/
|
||||||
|
- Auto generated documentation maintained in docs/
|
||||||
|
- Human generated documentation maintained in wiki/ submodule
|
||||||
- Test-driven development workflow with comprehensive test coverage
|
- Test-driven development workflow with comprehensive test coverage
|
||||||
|
|
||||||
|
Important: Respect the directory structure! If in doubt ask or use directories under tmp/ to keep the structure clean!
|
||||||
|
|
||||||
**Development Workflow:**
|
**Development Workflow:**
|
||||||
- Issue-driven development using Gitea API integration
|
- Issue-driven development using Gitea API integration
|
||||||
- Issue management via universal issue-facade CLI that works with multiple backends
|
- Issue management via universal issue-facade CLI that works with multiple backends
|
||||||
@@ -56,17 +62,19 @@ You are the MarkiTect project assistant, specialized in providing project status
|
|||||||
|
|
||||||
When asked about project status or next steps:
|
When asked about project status or next steps:
|
||||||
|
|
||||||
1. **Start with Current State**: Always check ProjectStatusDigest.md for the latest architecture and status
|
1. **Start with Current State**: Always check TODO.md for the latest activity
|
||||||
2. **Review Recent Progress**: Check ProjectDiary.md for recent accomplishments and context
|
2. **Review Recent Progress**: Check CHANGELOG.md for previous work and progress
|
||||||
3. **Check Planned Work**: Read Next.md for documented next steps and priorities
|
3. **Check Planned Work**: TODO.md documents next steps and priorities, if empty see topics in roadmap/
|
||||||
4. **Consider Git Status**: Be aware of current working directory state and recent commits
|
4. **Project Scope and Goals**: Vision, Mission, Guidelines and Usecases live in wiki/ if available
|
||||||
|
5. **Planning New Stuff**: Requirements (Epics and Stories) are gitea issues to be planned as roadmap topics
|
||||||
|
6. **Consider Git Status**: Allways be aware of current working directory state and recent commits
|
||||||
|
|
||||||
### Issue Management Guidelines
|
### Issue Management Guidelines
|
||||||
|
|
||||||
**When to Create Gitea Issues:**
|
**When to Create Gitea Issues:**
|
||||||
- New feature requests or enhancement ideas emerge during development
|
- New feature requests or enhancement ideas emerge during development
|
||||||
- Bugs or technical debt are discovered but not immediately fixable
|
- Bugs or technical debt are discovered but not immediately fixable
|
||||||
- Future improvements are identified but outside current session scope
|
- Future improvements are identified but outside current session and topic scope
|
||||||
- Architecture decisions require documentation and future review
|
- Architecture decisions require documentation and future review
|
||||||
- Sidequests that we want to remember for later implementation
|
- Sidequests that we want to remember for later implementation
|
||||||
|
|
||||||
@@ -78,10 +86,12 @@ When asked about project status or next steps:
|
|||||||
- Do NOT implement immediately - issues are for tracking and planning
|
- Do NOT implement immediately - issues are for tracking and planning
|
||||||
|
|
||||||
**Issue vs. Immediate Work:**
|
**Issue vs. Immediate Work:**
|
||||||
- Current session planned work: implement directly (from Next.md)
|
- Current session planned work: document in TODO.md and roadmap/ROADMAPTOPIC
|
||||||
- Discovered improvements: create issue, continue with planned work
|
- Discovered improvements: add to workplan in roadmap topic, continue with planned work
|
||||||
- Critical bugs affecting current work: fix immediately, then create issue for root cause analysis
|
- Critical bugs affecting current work: fix immediately, then create issue for root cause analysis
|
||||||
- Future enhancements: always create issue first for proper planning
|
- Future enhancements: note in roadmap-topic to create issues first for proper planning
|
||||||
|
- If possible create issues interactively when closing a topic, they are for human oversight and longterm
|
||||||
|
- Do not create issues for stuff that is detailed and can be adressed before closing the current topic
|
||||||
|
|
||||||
**Response Format:**
|
**Response Format:**
|
||||||
- Provide a brief status summary (2-3 sentences)
|
- Provide a brief status summary (2-3 sentences)
|
||||||
@@ -102,8 +112,6 @@ When asked about project status or next steps:
|
|||||||
1. [Action from Next.md or logical progression]
|
1. [Action from Next.md or logical progression]
|
||||||
2. [Secondary priority or alternative approach]
|
2. [Secondary priority or alternative approach]
|
||||||
3. [Maintenance or validation task if applicable]
|
3. [Maintenance or validation task if applicable]
|
||||||
|
|
||||||
Based on: ProjectStatusDigest.md:74-79, Next.md:7-13
|
|
||||||
```
|
```
|
||||||
|
|
||||||
## Session Start-Up Protocol
|
## Session Start-Up Protocol
|
||||||
@@ -113,10 +121,10 @@ When asked what's up for a new coding session, follow this standardized routine:
|
|||||||
### Start-of-Session Checklist
|
### Start-of-Session Checklist
|
||||||
1. **Mission Status**: Provide reminder to project vision and how we are doing
|
1. **Mission Status**: Provide reminder to project vision and how we are doing
|
||||||
2. **Recently**: Provide reminder what we did last from the last entry to the diary
|
2. **Recently**: Provide reminder what we did last from the last entry to the diary
|
||||||
3. **NEXT.txt**: Check if we provided guidance for what to do next at the end of the last coding session
|
3. **TODO.md**: Check if we provided guidance for what to do next at the end of the last coding session
|
||||||
4. **git status**: Check if git is clean or work has been left unfinished
|
4. **git status**: Check if git is clean or work has been left unfinished
|
||||||
5. **Workspace clean**: Check if workspace is clean or we left of in the middle of a TDD cycle
|
5. **Workspace clean**: Check if workspace is clean or we left of in the middle of a TDD cycle
|
||||||
6. **Issue finished**: Check if we are currently working on a specific issue or need to select the next one
|
6. **Topic or issue finished**: Check if we are currently working on a specific roadmap-topic or issue
|
||||||
7. **Suggestion**: Provide a sensible suggestion of what to do next
|
7. **Suggestion**: Provide a sensible suggestion of what to do next
|
||||||
|
|
||||||
## Session Wrap-Up Protocol
|
## Session Wrap-Up Protocol
|
||||||
@@ -124,11 +132,10 @@ When asked what's up for a new coding session, follow this standardized routine:
|
|||||||
When asked to help wrap up a development session, follow this standardized routine:
|
When asked to help wrap up a development session, follow this standardized routine:
|
||||||
|
|
||||||
### End-of-Session Checklist:
|
### End-of-Session Checklist:
|
||||||
1. **Update ProjectDiary.md**: Add entry documenting progress, challenges, and achievements
|
|
||||||
2. **Update TODO.md**: Set clear priorities and strategy for next session using todofile format
|
2. **Update TODO.md**: Set clear priorities and strategy for next session using todofile format
|
||||||
3. **Update ProjectStatusDigest.md**: Refresh current status, metrics, and completed features
|
3. **Update roadmap-topic directory information**: Refresh current status, metrics, and completed features
|
||||||
4. **Issue Management**: Review and create any issues for sidequests and discoveries made during session
|
4. **Issue Management**: Review and create any issues for sidequests and discoveries made during session
|
||||||
5. **Anchor patterns**: Update this project-assistant definition with any new workflow patterns
|
5. **Anchor patterns**: Add Update suggestions for this project-assistant definition with any new workflow patterns
|
||||||
6. **Prepare for commit**: Ensure all documentation reflects current state
|
6. **Prepare for commit**: Ensure all documentation reflects current state
|
||||||
|
|
||||||
### Session Success Indicators:
|
### Session Success Indicators:
|
||||||
@@ -143,9 +150,9 @@ When asked to help wrap up a development session, follow this standardized routi
|
|||||||
[Brief overview of accomplishments and current state]
|
[Brief overview of accomplishments and current state]
|
||||||
|
|
||||||
## Documentation Updates
|
## Documentation Updates
|
||||||
- ✅ ProjectDiary.md: [what was added]
|
- ✅ TODO.md: [priorities set]
|
||||||
- ✅ Next.md: [priorities set]
|
- ✅ roadmap/TOPIC files: [what was added or changed]
|
||||||
- ✅ ProjectStatusDigest.md: [status updated]
|
- ✅ CHANGELOG.ms: [status updated especially on release]
|
||||||
|
|
||||||
## Issues Created/Updated
|
## Issues Created/Updated
|
||||||
- 🎯 Issue #X: [brief description] - [reason for creation]
|
- 🎯 Issue #X: [brief description] - [reason for creation]
|
||||||
@@ -157,9 +164,19 @@ When asked to help wrap up a development session, follow this standardized routi
|
|||||||
Ready for commit: [list of files to commit]
|
Ready for commit: [list of files to commit]
|
||||||
```
|
```
|
||||||
|
|
||||||
|
### Example Capture Small Off-Topic Improvements in roadmap/eat-the-frog:
|
||||||
|
**Smell**: Different filename conventions od conflicting concepts, unclear guideance
|
||||||
|
**Hunch**: Ideas to explore that need consideration if useful and in scope
|
||||||
|
**Hickups**: Notes on inefficient or roundtripping implementation to analyse later
|
||||||
|
|
||||||
|
Collect these in the roadmap-topic-directory and move stuff to eat-the-frog on close if unfinished
|
||||||
|
|
||||||
### Example Issue Creation During Development:
|
### Example Issue Creation During Development:
|
||||||
**Scenario**: While implementing CLI commands, discover that error messages could be improved
|
**Scenario**: While implementing CLI commands, discover that error messages could be improved
|
||||||
**Action**: Create issue "Enhance CLI error messages with user-friendly formatting and suggestions"
|
**Action**: Create issue "Enhance CLI error messages with user-friendly formatting and suggestions"
|
||||||
**Result**: Continue with current CLI implementation, address error enhancement in future session
|
**Result**: Continue with current CLI implementation, address error enhancement in future session
|
||||||
|
|
||||||
|
Generate issues for relevantly expensive or risky stuff and in direct feedback with developers.
|
||||||
|
Controled in-scope-work does not need the costly issue capture, refinement, selection roundtrip.
|
||||||
|
|
||||||
Remember: Your role is to help developers quickly understand "where we are" and "what should we do next" when picking up work on the MarkiTect project, and to ensure proper session wrap-up for continuity.
|
Remember: Your role is to help developers quickly understand "where we are" and "what should we do next" when picking up work on the MarkiTect project, and to ensure proper session wrap-up for continuity.
|
||||||
Submodule capabilities/kaizen-agentic updated: 1e0ff82d74...afc038d98b
@@ -0,0 +1,10 @@
|
|||||||
|
"""
|
||||||
|
CHANGELOG management tools.
|
||||||
|
|
||||||
|
This package provides tools for working with CHANGELOG.md files.
|
||||||
|
"""
|
||||||
|
|
||||||
|
from .editor import ChangelogEditor
|
||||||
|
from .parser import ChangelogParser
|
||||||
|
|
||||||
|
__all__ = ['ChangelogEditor', 'ChangelogParser']
|
||||||
@@ -0,0 +1,207 @@
|
|||||||
|
"""
|
||||||
|
CHANGELOG.md editor for programmatic updates.
|
||||||
|
|
||||||
|
This module provides tools for editing CHANGELOG.md files following
|
||||||
|
the Keep a Changelog format.
|
||||||
|
"""
|
||||||
|
|
||||||
|
from datetime import datetime
|
||||||
|
from pathlib import Path
|
||||||
|
from typing import Optional, List
|
||||||
|
|
||||||
|
|
||||||
|
class ChangelogEditor:
|
||||||
|
"""Programmatic editor for CHANGELOG.md files."""
|
||||||
|
|
||||||
|
def __init__(self, changelog_path: Optional[Path] = None):
|
||||||
|
"""Initialize changelog editor.
|
||||||
|
|
||||||
|
Args:
|
||||||
|
changelog_path: Path to CHANGELOG.md file
|
||||||
|
"""
|
||||||
|
self.changelog_path = changelog_path or Path.cwd() / 'CHANGELOG.md'
|
||||||
|
|
||||||
|
def create_version_section(self, version: str, date: Optional[str] = None) -> bool:
|
||||||
|
"""Create new version section and move Unreleased content.
|
||||||
|
|
||||||
|
Args:
|
||||||
|
version: Version number (e.g., "0.11.0")
|
||||||
|
date: Release date in YYYY-MM-DD format (defaults to today)
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
True if successful, False otherwise
|
||||||
|
"""
|
||||||
|
if date is None:
|
||||||
|
date = datetime.now().strftime("%Y-%m-%d")
|
||||||
|
|
||||||
|
# Validate version format
|
||||||
|
version_clean = version.lstrip('v')
|
||||||
|
|
||||||
|
if not self.changelog_path.exists():
|
||||||
|
print(f"❌ CHANGELOG.md not found at {self.changelog_path}")
|
||||||
|
return False
|
||||||
|
|
||||||
|
try:
|
||||||
|
with open(self.changelog_path) as f:
|
||||||
|
lines = f.readlines()
|
||||||
|
|
||||||
|
# Find Unreleased section
|
||||||
|
unreleased_idx = None
|
||||||
|
for i, line in enumerate(lines):
|
||||||
|
if line.strip() == "## [Unreleased]":
|
||||||
|
unreleased_idx = i
|
||||||
|
break
|
||||||
|
|
||||||
|
if unreleased_idx is None:
|
||||||
|
print("❌ No [Unreleased] section found in CHANGELOG.md")
|
||||||
|
return False
|
||||||
|
|
||||||
|
# Find next version section or end of Unreleased content
|
||||||
|
next_section_idx = None
|
||||||
|
for i in range(unreleased_idx + 1, len(lines)):
|
||||||
|
if lines[i].startswith("## [") and not lines[i].startswith("## [Unreleased]"):
|
||||||
|
next_section_idx = i
|
||||||
|
break
|
||||||
|
|
||||||
|
# Extract Unreleased content (skip the header line and first blank line)
|
||||||
|
if next_section_idx:
|
||||||
|
unreleased_content = lines[unreleased_idx + 1:next_section_idx]
|
||||||
|
else:
|
||||||
|
unreleased_content = lines[unreleased_idx + 1:]
|
||||||
|
|
||||||
|
# Check if there's actual content to move
|
||||||
|
has_content = any(line.strip() and not line.strip().startswith('#')
|
||||||
|
for line in unreleased_content)
|
||||||
|
|
||||||
|
if not has_content:
|
||||||
|
print(f"⚠️ [Unreleased] section is empty. Add changes before creating release section.")
|
||||||
|
print(f"💡 Tip: You can still create the section, but it will be empty.")
|
||||||
|
|
||||||
|
# Create new version section with moved content
|
||||||
|
new_section_lines = [
|
||||||
|
f"## [{version_clean}] - {date}\n",
|
||||||
|
]
|
||||||
|
|
||||||
|
# Add the unreleased content (preserving structure)
|
||||||
|
new_section_lines.extend(unreleased_content)
|
||||||
|
|
||||||
|
# Ensure proper spacing after new section
|
||||||
|
if new_section_lines and not new_section_lines[-1].endswith('\n\n'):
|
||||||
|
if not new_section_lines[-1].endswith('\n'):
|
||||||
|
new_section_lines[-1] += '\n'
|
||||||
|
new_section_lines.append('\n')
|
||||||
|
|
||||||
|
# Build new file content
|
||||||
|
# Keep everything up to and including Unreleased header
|
||||||
|
new_lines = lines[:unreleased_idx + 1]
|
||||||
|
|
||||||
|
# Add blank line after Unreleased header
|
||||||
|
new_lines.append('\n')
|
||||||
|
|
||||||
|
# Add the new version section
|
||||||
|
new_lines.extend(new_section_lines)
|
||||||
|
|
||||||
|
# Add remaining sections (if any)
|
||||||
|
if next_section_idx:
|
||||||
|
new_lines.extend(lines[next_section_idx:])
|
||||||
|
|
||||||
|
# Write back
|
||||||
|
with open(self.changelog_path, 'w') as f:
|
||||||
|
f.writelines(new_lines)
|
||||||
|
|
||||||
|
print(f"✅ Created section [{version_clean}] - {date} in CHANGELOG.md")
|
||||||
|
if has_content:
|
||||||
|
print(f"📝 Moved content from [Unreleased] to [{version_clean}]")
|
||||||
|
print(f"💡 [Unreleased] section is now empty and ready for new changes")
|
||||||
|
return True
|
||||||
|
|
||||||
|
except Exception as e:
|
||||||
|
print(f"❌ Error editing CHANGELOG.md: {e}")
|
||||||
|
return False
|
||||||
|
|
||||||
|
def get_version_content(self, version: str) -> Optional[List[str]]:
|
||||||
|
"""Extract content for a specific version section.
|
||||||
|
|
||||||
|
Args:
|
||||||
|
version: Version number to extract (e.g., "0.10.0")
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
List of lines in the version section, or None if not found
|
||||||
|
"""
|
||||||
|
version_clean = version.lstrip('v')
|
||||||
|
|
||||||
|
if not self.changelog_path.exists():
|
||||||
|
return None
|
||||||
|
|
||||||
|
try:
|
||||||
|
with open(self.changelog_path) as f:
|
||||||
|
lines = f.readlines()
|
||||||
|
|
||||||
|
# Find the version section
|
||||||
|
version_idx = None
|
||||||
|
for i, line in enumerate(lines):
|
||||||
|
if line.strip().startswith(f"## [{version_clean}]"):
|
||||||
|
version_idx = i
|
||||||
|
break
|
||||||
|
|
||||||
|
if version_idx is None:
|
||||||
|
return None
|
||||||
|
|
||||||
|
# Find next section
|
||||||
|
next_section_idx = None
|
||||||
|
for i in range(version_idx + 1, len(lines)):
|
||||||
|
if lines[i].startswith("## ["):
|
||||||
|
next_section_idx = i
|
||||||
|
break
|
||||||
|
|
||||||
|
# Extract content
|
||||||
|
if next_section_idx:
|
||||||
|
return lines[version_idx:next_section_idx]
|
||||||
|
else:
|
||||||
|
return lines[version_idx:]
|
||||||
|
|
||||||
|
except Exception:
|
||||||
|
return None
|
||||||
|
|
||||||
|
def has_unreleased_content(self) -> bool:
|
||||||
|
"""Check if Unreleased section has any content.
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
True if Unreleased section has content, False otherwise
|
||||||
|
"""
|
||||||
|
if not self.changelog_path.exists():
|
||||||
|
return False
|
||||||
|
|
||||||
|
try:
|
||||||
|
with open(self.changelog_path) as f:
|
||||||
|
lines = f.readlines()
|
||||||
|
|
||||||
|
# Find Unreleased section
|
||||||
|
unreleased_idx = None
|
||||||
|
for i, line in enumerate(lines):
|
||||||
|
if line.strip() == "## [Unreleased]":
|
||||||
|
unreleased_idx = i
|
||||||
|
break
|
||||||
|
|
||||||
|
if unreleased_idx is None:
|
||||||
|
return False
|
||||||
|
|
||||||
|
# Find next section
|
||||||
|
next_section_idx = None
|
||||||
|
for i in range(unreleased_idx + 1, len(lines)):
|
||||||
|
if lines[i].startswith("## ["):
|
||||||
|
next_section_idx = i
|
||||||
|
break
|
||||||
|
|
||||||
|
# Check content
|
||||||
|
if next_section_idx:
|
||||||
|
content = lines[unreleased_idx + 1:next_section_idx]
|
||||||
|
else:
|
||||||
|
content = lines[unreleased_idx + 1:]
|
||||||
|
|
||||||
|
# Check if there's actual content (not just whitespace or section headers)
|
||||||
|
return any(line.strip() and not line.strip().startswith('#')
|
||||||
|
for line in content)
|
||||||
|
|
||||||
|
except Exception:
|
||||||
|
return False
|
||||||
@@ -0,0 +1,179 @@
|
|||||||
|
"""
|
||||||
|
CHANGELOG.md parser for extracting release notes.
|
||||||
|
|
||||||
|
This module provides tools for parsing CHANGELOG.md files and extracting
|
||||||
|
version-specific content for release notes.
|
||||||
|
"""
|
||||||
|
|
||||||
|
import re
|
||||||
|
from pathlib import Path
|
||||||
|
from typing import Optional
|
||||||
|
|
||||||
|
|
||||||
|
class ChangelogParser:
|
||||||
|
"""Parse CHANGELOG.md files and extract release information."""
|
||||||
|
|
||||||
|
def __init__(self, changelog_path: Optional[Path] = None):
|
||||||
|
"""Initialize changelog parser.
|
||||||
|
|
||||||
|
Args:
|
||||||
|
changelog_path: Path to CHANGELOG.md file
|
||||||
|
"""
|
||||||
|
self.changelog_path = changelog_path or Path.cwd() / 'CHANGELOG.md'
|
||||||
|
|
||||||
|
def extract_version_section(self, version: str, format: str = 'markdown') -> str:
|
||||||
|
"""Extract CHANGELOG section for a specific version.
|
||||||
|
|
||||||
|
Args:
|
||||||
|
version: Version to extract (e.g., "0.10.0")
|
||||||
|
format: Output format ('markdown', 'plain', 'html')
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
Formatted content of the version section
|
||||||
|
"""
|
||||||
|
if not self.changelog_path.exists():
|
||||||
|
return f"Error: CHANGELOG.md not found at {self.changelog_path}"
|
||||||
|
|
||||||
|
try:
|
||||||
|
version_clean = version.lstrip('v')
|
||||||
|
|
||||||
|
with open(self.changelog_path) as f:
|
||||||
|
content = f.read()
|
||||||
|
|
||||||
|
# Find the version section using regex
|
||||||
|
# Match: ## [VERSION] - DATE followed by content until next ## [
|
||||||
|
pattern = rf"## \[{re.escape(version_clean)}\].*?\n\n(.*?)(?=\n## \[|\Z)"
|
||||||
|
match = re.search(pattern, content, re.DOTALL)
|
||||||
|
|
||||||
|
if not match:
|
||||||
|
return f"Error: No section found for version {version_clean} in CHANGELOG.md"
|
||||||
|
|
||||||
|
section_content = match.group(1).strip()
|
||||||
|
|
||||||
|
if not section_content:
|
||||||
|
return f"Warning: Section for version {version_clean} exists but is empty"
|
||||||
|
|
||||||
|
# Format based on requested format
|
||||||
|
if format == 'plain':
|
||||||
|
return self._to_plain(section_content)
|
||||||
|
elif format == 'html':
|
||||||
|
return self._to_html(section_content)
|
||||||
|
else:
|
||||||
|
return section_content # markdown (default)
|
||||||
|
|
||||||
|
except Exception as e:
|
||||||
|
return f"Error reading CHANGELOG: {e}"
|
||||||
|
|
||||||
|
def get_latest_version(self) -> Optional[str]:
|
||||||
|
"""Get the latest version number from CHANGELOG.
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
Latest version string or None if not found
|
||||||
|
"""
|
||||||
|
if not self.changelog_path.exists():
|
||||||
|
return None
|
||||||
|
|
||||||
|
try:
|
||||||
|
with open(self.changelog_path) as f:
|
||||||
|
content = f.read()
|
||||||
|
|
||||||
|
# Find first version section (skip Unreleased)
|
||||||
|
pattern = r"## \[(\d+\.\d+\.\d+[^\]]*)\]"
|
||||||
|
match = re.search(pattern, content)
|
||||||
|
|
||||||
|
return match.group(1) if match else None
|
||||||
|
|
||||||
|
except Exception:
|
||||||
|
return None
|
||||||
|
|
||||||
|
def list_versions(self) -> list:
|
||||||
|
"""List all versions in CHANGELOG.
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
List of version strings
|
||||||
|
"""
|
||||||
|
if not self.changelog_path.exists():
|
||||||
|
return []
|
||||||
|
|
||||||
|
try:
|
||||||
|
with open(self.changelog_path) as f:
|
||||||
|
content = f.read()
|
||||||
|
|
||||||
|
# Find all version sections (excluding Unreleased)
|
||||||
|
pattern = r"## \[(\d+\.\d+\.\d+[^\]]*)\]"
|
||||||
|
matches = re.findall(pattern, content)
|
||||||
|
|
||||||
|
return matches
|
||||||
|
|
||||||
|
except Exception:
|
||||||
|
return []
|
||||||
|
|
||||||
|
def _to_plain(self, markdown_content: str) -> str:
|
||||||
|
"""Convert markdown content to plain text.
|
||||||
|
|
||||||
|
Args:
|
||||||
|
markdown_content: Markdown formatted content
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
Plain text content
|
||||||
|
"""
|
||||||
|
# Remove markdown formatting
|
||||||
|
plain = markdown_content
|
||||||
|
|
||||||
|
# Remove bold/italic
|
||||||
|
plain = re.sub(r'\*\*([^*]+)\*\*', r'\1', plain) # bold
|
||||||
|
plain = re.sub(r'\*([^*]+)\*', r'\1', plain) # italic
|
||||||
|
plain = re.sub(r'__([^_]+)__', r'\1', plain) # bold (underscores)
|
||||||
|
plain = re.sub(r'_([^_]+)_', r'\1', plain) # italic (underscores)
|
||||||
|
|
||||||
|
# Remove links but keep text
|
||||||
|
plain = re.sub(r'\[([^\]]+)\]\([^\)]+\)', r'\1', plain)
|
||||||
|
|
||||||
|
# Remove inline code backticks
|
||||||
|
plain = re.sub(r'`([^`]+)`', r'\1', plain)
|
||||||
|
|
||||||
|
# Convert headers to plain text with spacing
|
||||||
|
plain = re.sub(r'^### (.+)$', r'\n\1:', plain, flags=re.MULTILINE)
|
||||||
|
plain = re.sub(r'^## (.+)$', r'\n\1\n' + '=' * 40, plain, flags=re.MULTILINE)
|
||||||
|
|
||||||
|
return plain.strip()
|
||||||
|
|
||||||
|
def _to_html(self, markdown_content: str) -> str:
|
||||||
|
"""Convert markdown content to HTML.
|
||||||
|
|
||||||
|
Args:
|
||||||
|
markdown_content: Markdown formatted content
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
HTML formatted content
|
||||||
|
"""
|
||||||
|
try:
|
||||||
|
import markdown
|
||||||
|
return markdown.markdown(markdown_content)
|
||||||
|
except ImportError:
|
||||||
|
# Fallback to basic HTML conversion if markdown package not available
|
||||||
|
html = markdown_content
|
||||||
|
|
||||||
|
# Headers
|
||||||
|
html = re.sub(r'^### (.+)$', r'<h3>\1</h3>', html, flags=re.MULTILINE)
|
||||||
|
html = re.sub(r'^## (.+)$', r'<h2>\1</h2>', html, flags=re.MULTILINE)
|
||||||
|
|
||||||
|
# Bold/italic
|
||||||
|
html = re.sub(r'\*\*([^*]+)\*\*', r'<strong>\1</strong>', html)
|
||||||
|
html = re.sub(r'\*([^*]+)\*', r'<em>\1</em>', html)
|
||||||
|
|
||||||
|
# Links
|
||||||
|
html = re.sub(r'\[([^\]]+)\]\(([^\)]+)\)', r'<a href="\2">\1</a>', html)
|
||||||
|
|
||||||
|
# Code
|
||||||
|
html = re.sub(r'`([^`]+)`', r'<code>\1</code>', html)
|
||||||
|
|
||||||
|
# Lists
|
||||||
|
html = re.sub(r'^- (.+)$', r'<li>\1</li>', html, flags=re.MULTILINE)
|
||||||
|
html = re.sub(r'(<li>.*</li>)', r'<ul>\1</ul>', html, flags=re.DOTALL)
|
||||||
|
|
||||||
|
# Paragraphs
|
||||||
|
html = re.sub(r'\n\n', '</p><p>', html)
|
||||||
|
html = f'<p>{html}</p>'
|
||||||
|
|
||||||
|
return html
|
||||||
@@ -11,6 +11,9 @@ from typing import Optional
|
|||||||
|
|
||||||
from ..core.manager import ReleaseManager
|
from ..core.manager import ReleaseManager
|
||||||
from ..utils.version import VersionManager
|
from ..utils.version import VersionManager
|
||||||
|
from ..changelog.editor import ChangelogEditor
|
||||||
|
from ..changelog.parser import ChangelogParser
|
||||||
|
from ..summary.generator import SummaryGenerator
|
||||||
|
|
||||||
|
|
||||||
@click.group(invoke_without_command=True)
|
@click.group(invoke_without_command=True)
|
||||||
@@ -55,6 +58,15 @@ def status(ctx):
|
|||||||
print(f"Latest Commit: {status_info['latest_commit']}")
|
print(f"Latest Commit: {status_info['latest_commit']}")
|
||||||
print(f"Latest Tag: {status_info['latest_tag'] or 'None'}")
|
print(f"Latest Tag: {status_info['latest_tag'] or 'None'}")
|
||||||
print(f"Uncommitted Changes: {'Yes' if status_info['has_changes'] else 'No'}")
|
print(f"Uncommitted Changes: {'Yes' if status_info['has_changes'] else 'No'}")
|
||||||
|
|
||||||
|
# Show unpushed tags
|
||||||
|
unpushed_tags = status_info.get('unpushed_tags', [])
|
||||||
|
if unpushed_tags:
|
||||||
|
print(f"\n⚠️ Unpushed Tags: {len(unpushed_tags)} tag(s) not pushed to origin")
|
||||||
|
for tag in unpushed_tags:
|
||||||
|
print(f" - {tag}")
|
||||||
|
print(f"\n💡 Push tags with: git push origin {' '.join(unpushed_tags)}")
|
||||||
|
print(f" Or push all tags: git push --tags")
|
||||||
else:
|
else:
|
||||||
print("Git Repository: Not available")
|
print("Git Repository: Not available")
|
||||||
|
|
||||||
@@ -104,8 +116,10 @@ def validate(ctx):
|
|||||||
@main.command()
|
@main.command()
|
||||||
@click.option('--version', required=True, help='Version to tag (e.g., 0.8.0)')
|
@click.option('--version', required=True, help='Version to tag (e.g., 0.8.0)')
|
||||||
@click.option('--message', help='Tag message')
|
@click.option('--message', help='Tag message')
|
||||||
|
@click.option('--push/--no-push', default=True,
|
||||||
|
help='Automatically push tag to origin (default: --push)')
|
||||||
@click.pass_context
|
@click.pass_context
|
||||||
def tag(ctx, version: str, message: Optional[str]):
|
def tag(ctx, version: str, message: Optional[str], push: bool):
|
||||||
"""Create git tag for version."""
|
"""Create git tag for version."""
|
||||||
manager = ReleaseManager(
|
manager = ReleaseManager(
|
||||||
project_root=ctx.obj['project_root'],
|
project_root=ctx.obj['project_root'],
|
||||||
@@ -113,8 +127,10 @@ def tag(ctx, version: str, message: Optional[str]):
|
|||||||
force=ctx.obj['force']
|
force=ctx.obj['force']
|
||||||
)
|
)
|
||||||
|
|
||||||
if manager.create_tag(version, message):
|
if manager.create_tag(version, message, push=push):
|
||||||
print(f"✅ Successfully created tag for version {version}")
|
print(f"✅ Successfully created tag for version {version}")
|
||||||
|
if not push:
|
||||||
|
print(f"💡 Push tag with: git push origin v{version}")
|
||||||
else:
|
else:
|
||||||
print(f"❌ Failed to create tag for version {version}")
|
print(f"❌ Failed to create tag for version {version}")
|
||||||
sys.exit(1)
|
sys.exit(1)
|
||||||
@@ -248,5 +264,153 @@ def version_info(ctx, suggest: bool):
|
|||||||
print(f" {key.replace('_', ' ').title()}: {value}")
|
print(f" {key.replace('_', ' ').title()}: {value}")
|
||||||
|
|
||||||
|
|
||||||
|
@main.command('check-consistency')
|
||||||
|
@click.option('--version', required=True, help='Version to check (e.g., 0.10.0)')
|
||||||
|
@click.pass_context
|
||||||
|
def check_consistency(ctx, version: str):
|
||||||
|
"""Check consistency between CHANGELOG version and git tags."""
|
||||||
|
manager = ReleaseManager(
|
||||||
|
project_root=ctx.obj['project_root'],
|
||||||
|
dry_run=ctx.obj['dry_run'],
|
||||||
|
force=ctx.obj['force']
|
||||||
|
)
|
||||||
|
|
||||||
|
is_consistent, issues = manager.check_version_consistency(version)
|
||||||
|
|
||||||
|
if is_consistent:
|
||||||
|
print(f"✅ Version {version} is consistent:")
|
||||||
|
print(f" - CHANGELOG has section for {version}")
|
||||||
|
print(f" - Git tag v{version} exists")
|
||||||
|
print(f" - [Unreleased] section present")
|
||||||
|
else:
|
||||||
|
print(f"❌ Version {version} has consistency issues:")
|
||||||
|
for issue in issues:
|
||||||
|
print(f" - {issue}")
|
||||||
|
sys.exit(1)
|
||||||
|
|
||||||
|
|
||||||
|
@main.command('prepare')
|
||||||
|
@click.argument('version')
|
||||||
|
@click.option('--date', default=None, help='Release date (YYYY-MM-DD, defaults to today)')
|
||||||
|
@click.pass_context
|
||||||
|
def prepare(ctx, version: str, date: Optional[str]):
|
||||||
|
"""Prepare CHANGELOG for new version release.
|
||||||
|
|
||||||
|
Creates a new version section in CHANGELOG.md and moves all content
|
||||||
|
from the [Unreleased] section to the new version section.
|
||||||
|
"""
|
||||||
|
project_root = ctx.obj['project_root'] or Path.cwd()
|
||||||
|
changelog_path = project_root / 'CHANGELOG.md'
|
||||||
|
|
||||||
|
editor = ChangelogEditor(changelog_path)
|
||||||
|
|
||||||
|
# Create version section
|
||||||
|
if editor.create_version_section(version, date):
|
||||||
|
# Validate result
|
||||||
|
manager = ReleaseManager(
|
||||||
|
project_root=ctx.obj['project_root'],
|
||||||
|
dry_run=ctx.obj['dry_run'],
|
||||||
|
force=ctx.obj['force']
|
||||||
|
)
|
||||||
|
|
||||||
|
# Check if CHANGELOG is valid after edit
|
||||||
|
is_valid, issues = manager.validate_release_state()
|
||||||
|
|
||||||
|
if is_valid:
|
||||||
|
print("\n✅ CHANGELOG validation passed")
|
||||||
|
else:
|
||||||
|
print("\n⚠️ CHANGELOG validation issues after edit:")
|
||||||
|
for issue in issues:
|
||||||
|
if 'CHANGELOG' in issue:
|
||||||
|
print(f" - {issue}")
|
||||||
|
else:
|
||||||
|
print(f"❌ Failed to prepare CHANGELOG for version {version}")
|
||||||
|
sys.exit(1)
|
||||||
|
|
||||||
|
|
||||||
|
@main.command('summary')
|
||||||
|
@click.argument('version')
|
||||||
|
@click.option('--output', '-o', default=None, type=click.Path(path_type=Path),
|
||||||
|
help='Output file path (defaults to RELEASE_SUMMARY_vX.Y.Z.md)')
|
||||||
|
@click.pass_context
|
||||||
|
def summary(ctx, version: str, output: Optional[Path]):
|
||||||
|
"""Generate release summary document.
|
||||||
|
|
||||||
|
Extracts CHANGELOG content, git statistics, build artifacts, and
|
||||||
|
validation results to create a comprehensive release summary.
|
||||||
|
"""
|
||||||
|
project_root = ctx.obj['project_root'] or Path.cwd()
|
||||||
|
|
||||||
|
# Default output path
|
||||||
|
if output is None:
|
||||||
|
version_clean = version.lstrip('v')
|
||||||
|
output = project_root / f"RELEASE_SUMMARY_v{version_clean}.md"
|
||||||
|
elif not output.is_absolute():
|
||||||
|
output = project_root / output
|
||||||
|
|
||||||
|
generator = SummaryGenerator(project_root)
|
||||||
|
|
||||||
|
try:
|
||||||
|
content = generator.generate(version, output_path=output)
|
||||||
|
print(f"\n✅ Release summary generated successfully")
|
||||||
|
print(f"📄 Summary saved to: {output}")
|
||||||
|
except Exception as e:
|
||||||
|
print(f"❌ Failed to generate release summary: {e}")
|
||||||
|
sys.exit(1)
|
||||||
|
|
||||||
|
|
||||||
|
@main.command('notes')
|
||||||
|
@click.argument('version', required=False)
|
||||||
|
@click.option('--format', 'output_format', type=click.Choice(['markdown', 'plain', 'html']),
|
||||||
|
default='markdown', help='Output format (default: markdown)')
|
||||||
|
@click.option('--output', '-o', default=None, type=click.Path(path_type=Path),
|
||||||
|
help='Save to file instead of stdout')
|
||||||
|
@click.pass_context
|
||||||
|
def notes(ctx, version: Optional[str], output_format: str, output: Optional[Path]):
|
||||||
|
"""Extract release notes from CHANGELOG.md.
|
||||||
|
|
||||||
|
Extracts the CHANGELOG section for a specific version and outputs it
|
||||||
|
in various formats. Useful for creating GitHub/Gitea release notes.
|
||||||
|
|
||||||
|
If no version is specified, uses the latest version from CHANGELOG.
|
||||||
|
|
||||||
|
Examples:
|
||||||
|
release notes 0.10.0 # Extract v0.10.0 notes (markdown)
|
||||||
|
release notes # Extract latest version notes
|
||||||
|
release notes 0.10.0 --format plain # Plain text format
|
||||||
|
release notes 0.10.0 -o notes.md # Save to file
|
||||||
|
release notes 0.10.0 | gh release create v0.10.0 -F - # Pipe to gh
|
||||||
|
"""
|
||||||
|
project_root = ctx.obj['project_root'] or Path.cwd()
|
||||||
|
changelog_path = project_root / 'CHANGELOG.md'
|
||||||
|
|
||||||
|
parser = ChangelogParser(changelog_path)
|
||||||
|
|
||||||
|
# Get version (use latest if not specified)
|
||||||
|
if version is None:
|
||||||
|
version = parser.get_latest_version()
|
||||||
|
if version is None:
|
||||||
|
print("❌ Could not determine version from CHANGELOG.md")
|
||||||
|
sys.exit(1)
|
||||||
|
print(f"Using latest version: {version}", file=sys.stderr)
|
||||||
|
|
||||||
|
# Extract content
|
||||||
|
content = parser.extract_version_section(version, format=output_format)
|
||||||
|
|
||||||
|
# Check for errors
|
||||||
|
if content.startswith("Error:") or content.startswith("Warning:"):
|
||||||
|
print(content)
|
||||||
|
sys.exit(1 if content.startswith("Error:") else 0)
|
||||||
|
|
||||||
|
# Output
|
||||||
|
if output:
|
||||||
|
if not output.is_absolute():
|
||||||
|
output = project_root / output
|
||||||
|
output.write_text(content)
|
||||||
|
print(f"✅ Release notes written to {output}", file=sys.stderr)
|
||||||
|
else:
|
||||||
|
print(content)
|
||||||
|
|
||||||
|
|
||||||
if __name__ == '__main__':
|
if __name__ == '__main__':
|
||||||
main()
|
main()
|
||||||
@@ -75,12 +75,13 @@ class ReleaseManager:
|
|||||||
"""
|
"""
|
||||||
return self.validator.validate_release_state(force=self.force)
|
return self.validator.validate_release_state(force=self.force)
|
||||||
|
|
||||||
def create_tag(self, version: str, message: Optional[str] = None) -> bool:
|
def create_tag(self, version: str, message: Optional[str] = None, push: bool = True) -> bool:
|
||||||
"""Create a git tag for the release.
|
"""Create a git tag for the release.
|
||||||
|
|
||||||
Args:
|
Args:
|
||||||
version: Version to tag (e.g., "1.0.0")
|
version: Version to tag (e.g., "1.0.0")
|
||||||
message: Optional tag message
|
message: Optional tag message
|
||||||
|
push: Whether to push the tag to origin (default: True)
|
||||||
|
|
||||||
Returns:
|
Returns:
|
||||||
True if tag created successfully, False otherwise
|
True if tag created successfully, False otherwise
|
||||||
@@ -93,7 +94,16 @@ class ReleaseManager:
|
|||||||
print(f" - {issue}")
|
print(f" - {issue}")
|
||||||
return False
|
return False
|
||||||
|
|
||||||
return self.git_manager.create_tag(version, message)
|
# Check version-tag consistency (ensure CHANGELOG has version section)
|
||||||
|
changelog_valid, changelog_issues = self.validator.validate_changelog_version(version)
|
||||||
|
if not changelog_valid and not self.force:
|
||||||
|
print(f"❌ Cannot create tag for version {version}:")
|
||||||
|
for issue in changelog_issues:
|
||||||
|
print(f" - {issue}")
|
||||||
|
print("\n💡 Tip: Add a section for version {version} to CHANGELOG.md before tagging")
|
||||||
|
return False
|
||||||
|
|
||||||
|
return self.git_manager.create_tag(version, message, push=push)
|
||||||
|
|
||||||
def build_packages(self) -> bool:
|
def build_packages(self) -> bool:
|
||||||
"""Build release packages.
|
"""Build release packages.
|
||||||
@@ -212,4 +222,15 @@ class ReleaseManager:
|
|||||||
Returns:
|
Returns:
|
||||||
List of commit messages since last tag
|
List of commit messages since last tag
|
||||||
"""
|
"""
|
||||||
return self.git_manager.get_commits_since_tag()
|
return self.git_manager.get_commits_since_tag()
|
||||||
|
|
||||||
|
def check_version_consistency(self, version: str) -> Tuple[bool, List[str]]:
|
||||||
|
"""Check consistency between CHANGELOG version and git tags.
|
||||||
|
|
||||||
|
Args:
|
||||||
|
version: Version to check (e.g., "0.10.0")
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
Tuple of (is_consistent, list_of_issues)
|
||||||
|
"""
|
||||||
|
return self.validator.check_version_tag_consistency(version)
|
||||||
@@ -48,22 +48,27 @@ class GitManager:
|
|||||||
except subprocess.CalledProcessError:
|
except subprocess.CalledProcessError:
|
||||||
latest_tag = None
|
latest_tag = None
|
||||||
|
|
||||||
|
# Get unpushed tags
|
||||||
|
unpushed_tags = self.get_unpushed_tags()
|
||||||
|
|
||||||
return {
|
return {
|
||||||
'is_repo': True,
|
'is_repo': True,
|
||||||
'branch': current_branch,
|
'branch': current_branch,
|
||||||
'has_changes': has_changes,
|
'has_changes': has_changes,
|
||||||
'latest_commit': latest_commit,
|
'latest_commit': latest_commit,
|
||||||
'latest_tag': latest_tag
|
'latest_tag': latest_tag,
|
||||||
|
'unpushed_tags': unpushed_tags
|
||||||
}
|
}
|
||||||
except subprocess.CalledProcessError:
|
except subprocess.CalledProcessError:
|
||||||
return {'is_repo': False}
|
return {'is_repo': False}
|
||||||
|
|
||||||
def create_tag(self, version: str, message: Optional[str] = None) -> bool:
|
def create_tag(self, version: str, message: Optional[str] = None, push: bool = True) -> bool:
|
||||||
"""Create and push git tag.
|
"""Create and optionally push git tag.
|
||||||
|
|
||||||
Args:
|
Args:
|
||||||
version: Version to tag (e.g., "1.0.0")
|
version: Version to tag (e.g., "1.0.0")
|
||||||
message: Optional tag message
|
message: Optional tag message
|
||||||
|
push: Whether to push the tag to origin (default: True)
|
||||||
|
|
||||||
Returns:
|
Returns:
|
||||||
True if successful, False otherwise
|
True if successful, False otherwise
|
||||||
@@ -81,16 +86,19 @@ class GitManager:
|
|||||||
self._run_command(['git', 'tag', '-a', tag_name, '-m', tag_message])
|
self._run_command(['git', 'tag', '-a', tag_name, '-m', tag_message])
|
||||||
print(f"✅ Tag {tag_name} created")
|
print(f"✅ Tag {tag_name} created")
|
||||||
|
|
||||||
# Push tag to origin
|
# Push tag to origin if requested
|
||||||
try:
|
if push:
|
||||||
print(f"📤 Pushing tag to origin...")
|
try:
|
||||||
self._run_command(['git', 'push', 'origin', tag_name])
|
print(f"📤 Pushing tag to origin...")
|
||||||
print(f"✅ Tag pushed to origin")
|
self._run_command(['git', 'push', 'origin', tag_name])
|
||||||
return True
|
print(f"✅ Tag pushed to origin")
|
||||||
except subprocess.CalledProcessError as e:
|
return True
|
||||||
print(f"⚠️ Could not push tag to origin: {e}")
|
except subprocess.CalledProcessError as e:
|
||||||
print(f"You can push it manually with: git push origin {tag_name}")
|
print(f"⚠️ Could not push tag to origin: {e}")
|
||||||
return True # Tag created successfully, push can be done manually
|
print(f"You can push it manually with: git push origin {tag_name}")
|
||||||
|
return True # Tag created successfully, push can be done manually
|
||||||
|
else:
|
||||||
|
return True # Tag created successfully, user chose not to push
|
||||||
|
|
||||||
except subprocess.CalledProcessError as e:
|
except subprocess.CalledProcessError as e:
|
||||||
print(f"❌ Failed to create tag: {e}")
|
print(f"❌ Failed to create tag: {e}")
|
||||||
@@ -178,6 +186,47 @@ class GitManager:
|
|||||||
except subprocess.CalledProcessError:
|
except subprocess.CalledProcessError:
|
||||||
return None
|
return None
|
||||||
|
|
||||||
|
def get_unpushed_tags(self, remote: str = 'origin') -> List[str]:
|
||||||
|
"""Get list of tags that exist locally but not on remote.
|
||||||
|
|
||||||
|
Args:
|
||||||
|
remote: Remote name to compare against (default: 'origin')
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
List of unpushed tag names
|
||||||
|
"""
|
||||||
|
try:
|
||||||
|
# Get local tags
|
||||||
|
local_result = self._run_command(['git', 'tag', '-l'])
|
||||||
|
local_tags = set(tag.strip() for tag in local_result.stdout.strip().split('\n') if tag.strip())
|
||||||
|
|
||||||
|
# Get remote tags
|
||||||
|
try:
|
||||||
|
remote_result = self._run_command(['git', 'ls-remote', '--tags', remote])
|
||||||
|
remote_lines = remote_result.stdout.strip().split('\n')
|
||||||
|
|
||||||
|
# Parse remote tags (format: "hash refs/tags/tagname")
|
||||||
|
remote_tags = set()
|
||||||
|
for line in remote_lines:
|
||||||
|
if not line:
|
||||||
|
continue
|
||||||
|
parts = line.split('refs/tags/')
|
||||||
|
if len(parts) > 1:
|
||||||
|
# Remove ^{} suffix for annotated tags
|
||||||
|
tag_name = parts[1].replace('^{}', '')
|
||||||
|
remote_tags.add(tag_name)
|
||||||
|
|
||||||
|
# Find tags that are local but not remote
|
||||||
|
unpushed = sorted(local_tags - remote_tags)
|
||||||
|
return unpushed
|
||||||
|
|
||||||
|
except subprocess.CalledProcessError:
|
||||||
|
# Remote not available, assume all tags are unpushed
|
||||||
|
return sorted(local_tags)
|
||||||
|
|
||||||
|
except subprocess.CalledProcessError:
|
||||||
|
return []
|
||||||
|
|
||||||
def _run_command(self, cmd: List[str]) -> subprocess.CompletedProcess:
|
def _run_command(self, cmd: List[str]) -> subprocess.CompletedProcess:
|
||||||
"""Run a git command.
|
"""Run a git command.
|
||||||
|
|
||||||
|
|||||||
@@ -0,0 +1,9 @@
|
|||||||
|
"""
|
||||||
|
Release summary generation tools.
|
||||||
|
|
||||||
|
This package provides tools for generating release summary documents.
|
||||||
|
"""
|
||||||
|
|
||||||
|
from .generator import SummaryGenerator
|
||||||
|
|
||||||
|
__all__ = ['SummaryGenerator']
|
||||||
@@ -0,0 +1,305 @@
|
|||||||
|
"""
|
||||||
|
Release summary generator.
|
||||||
|
|
||||||
|
This module generates comprehensive release summary documents from
|
||||||
|
CHANGELOG content and git metadata.
|
||||||
|
"""
|
||||||
|
|
||||||
|
import subprocess
|
||||||
|
from datetime import datetime
|
||||||
|
from pathlib import Path
|
||||||
|
from typing import Optional, Dict, Any, List
|
||||||
|
import re
|
||||||
|
|
||||||
|
|
||||||
|
class SummaryGenerator:
|
||||||
|
"""Generate release summary documents."""
|
||||||
|
|
||||||
|
def __init__(self, project_root: Optional[Path] = None):
|
||||||
|
"""Initialize summary generator.
|
||||||
|
|
||||||
|
Args:
|
||||||
|
project_root: Root directory of the project
|
||||||
|
"""
|
||||||
|
self.project_root = project_root or Path.cwd()
|
||||||
|
self.changelog_path = self.project_root / 'CHANGELOG.md'
|
||||||
|
self.dist_dir = self.project_root / 'dist'
|
||||||
|
|
||||||
|
def generate(self, version: str, output_path: Optional[Path] = None) -> str:
|
||||||
|
"""Generate release summary document.
|
||||||
|
|
||||||
|
Args:
|
||||||
|
version: Version to generate summary for (e.g., "0.10.0")
|
||||||
|
output_path: Optional path to write summary to
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
Generated summary content
|
||||||
|
"""
|
||||||
|
version_clean = version.lstrip('v')
|
||||||
|
tag_name = f"v{version_clean}"
|
||||||
|
|
||||||
|
# Get components
|
||||||
|
changelog_section = self.extract_changelog_section(version_clean)
|
||||||
|
git_stats = self.get_git_statistics(tag_name)
|
||||||
|
build_artifacts = self.list_build_artifacts()
|
||||||
|
validation_results = self.get_validation_results()
|
||||||
|
|
||||||
|
# Determine project name
|
||||||
|
project_name = self._get_project_name()
|
||||||
|
|
||||||
|
# Build summary
|
||||||
|
summary = f"""# {project_name} {version_clean} Release Summary
|
||||||
|
|
||||||
|
**Release Date**: {git_stats.get('release_date', 'Unknown')}
|
||||||
|
**Git Tag**: {tag_name}
|
||||||
|
**Commit**: {git_stats.get('commit_hash', 'Unknown')}
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Changes
|
||||||
|
|
||||||
|
{changelog_section}
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Git Statistics
|
||||||
|
|
||||||
|
- **Commits**: {git_stats.get('commit_count', 0)} commit(s) since last release
|
||||||
|
- **Files Changed**: {git_stats.get('files_changed', 0)} file(s)
|
||||||
|
- **Insertions**: +{git_stats.get('insertions', 0)} lines
|
||||||
|
- **Deletions**: -{git_stats.get('deletions', 0)} lines
|
||||||
|
- **Contributors**: {git_stats.get('contributors', 'Unknown')}
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Build Artifacts
|
||||||
|
|
||||||
|
{build_artifacts}
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Validation
|
||||||
|
|
||||||
|
{validation_results}
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
**Generated**: {datetime.now().strftime("%Y-%m-%d %H:%M:%S")}
|
||||||
|
"""
|
||||||
|
|
||||||
|
if output_path:
|
||||||
|
output_path.write_text(summary)
|
||||||
|
print(f"✅ Release summary written to {output_path}")
|
||||||
|
|
||||||
|
return summary
|
||||||
|
|
||||||
|
def extract_changelog_section(self, version: str) -> str:
|
||||||
|
"""Extract CHANGELOG section for a specific version.
|
||||||
|
|
||||||
|
Args:
|
||||||
|
version: Version to extract (e.g., "0.10.0")
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
Markdown content of the version section
|
||||||
|
"""
|
||||||
|
if not self.changelog_path.exists():
|
||||||
|
return "⚠️ CHANGELOG.md not found"
|
||||||
|
|
||||||
|
try:
|
||||||
|
with open(self.changelog_path) as f:
|
||||||
|
content = f.read()
|
||||||
|
|
||||||
|
# Find the version section
|
||||||
|
pattern = rf"## \[{re.escape(version)}\].*?\n\n(.*?)(?=\n## \[|\Z)"
|
||||||
|
match = re.search(pattern, content, re.DOTALL)
|
||||||
|
|
||||||
|
if match:
|
||||||
|
section_content = match.group(1).strip()
|
||||||
|
return section_content if section_content else "No changes documented"
|
||||||
|
else:
|
||||||
|
return f"⚠️ No section found for version {version} in CHANGELOG.md"
|
||||||
|
|
||||||
|
except Exception as e:
|
||||||
|
return f"❌ Error reading CHANGELOG: {e}"
|
||||||
|
|
||||||
|
def get_git_statistics(self, tag: str) -> Dict[str, Any]:
|
||||||
|
"""Get git statistics for a release tag.
|
||||||
|
|
||||||
|
Args:
|
||||||
|
tag: Git tag name (e.g., "v0.10.0")
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
Dictionary with git statistics
|
||||||
|
"""
|
||||||
|
stats = {}
|
||||||
|
|
||||||
|
try:
|
||||||
|
# Get tag date
|
||||||
|
try:
|
||||||
|
result = subprocess.run(
|
||||||
|
['git', 'log', '-1', '--format=%ci', tag],
|
||||||
|
capture_output=True, text=True, check=True, cwd=self.project_root
|
||||||
|
)
|
||||||
|
date_str = result.stdout.strip()
|
||||||
|
# Parse to get just the date
|
||||||
|
stats['release_date'] = date_str.split()[0] if date_str else 'Unknown'
|
||||||
|
except subprocess.CalledProcessError:
|
||||||
|
stats['release_date'] = 'Unknown'
|
||||||
|
|
||||||
|
# Get commit hash
|
||||||
|
try:
|
||||||
|
result = subprocess.run(
|
||||||
|
['git', 'rev-parse', tag],
|
||||||
|
capture_output=True, text=True, check=True, cwd=self.project_root
|
||||||
|
)
|
||||||
|
stats['commit_hash'] = result.stdout.strip()[:8]
|
||||||
|
except subprocess.CalledProcessError:
|
||||||
|
stats['commit_hash'] = 'Unknown'
|
||||||
|
|
||||||
|
# Find previous tag
|
||||||
|
try:
|
||||||
|
result = subprocess.run(
|
||||||
|
['git', 'describe', '--tags', '--abbrev=0', f'{tag}^'],
|
||||||
|
capture_output=True, text=True, check=True, cwd=self.project_root
|
||||||
|
)
|
||||||
|
previous_tag = result.stdout.strip()
|
||||||
|
except subprocess.CalledProcessError:
|
||||||
|
# No previous tag, use initial commit
|
||||||
|
previous_tag = None
|
||||||
|
|
||||||
|
# Get commit count
|
||||||
|
if previous_tag:
|
||||||
|
try:
|
||||||
|
result = subprocess.run(
|
||||||
|
['git', 'rev-list', '--count', f'{previous_tag}..{tag}'],
|
||||||
|
capture_output=True, text=True, check=True, cwd=self.project_root
|
||||||
|
)
|
||||||
|
stats['commit_count'] = int(result.stdout.strip())
|
||||||
|
except subprocess.CalledProcessError:
|
||||||
|
stats['commit_count'] = 0
|
||||||
|
else:
|
||||||
|
try:
|
||||||
|
result = subprocess.run(
|
||||||
|
['git', 'rev-list', '--count', tag],
|
||||||
|
capture_output=True, text=True, check=True, cwd=self.project_root
|
||||||
|
)
|
||||||
|
stats['commit_count'] = int(result.stdout.strip())
|
||||||
|
except subprocess.CalledProcessError:
|
||||||
|
stats['commit_count'] = 0
|
||||||
|
|
||||||
|
# Get file changes, insertions, deletions
|
||||||
|
if previous_tag:
|
||||||
|
diff_range = f'{previous_tag}..{tag}'
|
||||||
|
else:
|
||||||
|
diff_range = tag
|
||||||
|
|
||||||
|
try:
|
||||||
|
result = subprocess.run(
|
||||||
|
['git', 'diff', '--shortstat', diff_range],
|
||||||
|
capture_output=True, text=True, check=True, cwd=self.project_root
|
||||||
|
)
|
||||||
|
shortstat = result.stdout.strip()
|
||||||
|
|
||||||
|
# Parse shortstat: "X files changed, Y insertions(+), Z deletions(-)"
|
||||||
|
files_match = re.search(r'(\d+) files? changed', shortstat)
|
||||||
|
insert_match = re.search(r'(\d+) insertions?', shortstat)
|
||||||
|
delete_match = re.search(r'(\d+) deletions?', shortstat)
|
||||||
|
|
||||||
|
stats['files_changed'] = int(files_match.group(1)) if files_match else 0
|
||||||
|
stats['insertions'] = int(insert_match.group(1)) if insert_match else 0
|
||||||
|
stats['deletions'] = int(delete_match.group(1)) if delete_match else 0
|
||||||
|
except subprocess.CalledProcessError:
|
||||||
|
stats['files_changed'] = 0
|
||||||
|
stats['insertions'] = 0
|
||||||
|
stats['deletions'] = 0
|
||||||
|
|
||||||
|
# Get contributors
|
||||||
|
try:
|
||||||
|
result = subprocess.run(
|
||||||
|
['git', 'log', '--format=%an', f'{previous_tag}..{tag}' if previous_tag else tag],
|
||||||
|
capture_output=True, text=True, check=True, cwd=self.project_root
|
||||||
|
)
|
||||||
|
contributors = list(set(result.stdout.strip().split('\n')))
|
||||||
|
stats['contributors'] = ', '.join(contributors) if contributors and contributors[0] else 'Unknown'
|
||||||
|
except subprocess.CalledProcessError:
|
||||||
|
stats['contributors'] = 'Unknown'
|
||||||
|
|
||||||
|
except Exception as e:
|
||||||
|
print(f"⚠️ Error getting git statistics: {e}")
|
||||||
|
|
||||||
|
return stats
|
||||||
|
|
||||||
|
def list_build_artifacts(self) -> str:
|
||||||
|
"""List build artifacts in dist/ directory.
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
Formatted markdown list of build artifacts
|
||||||
|
"""
|
||||||
|
if not self.dist_dir.exists():
|
||||||
|
return "No build artifacts found (dist/ directory does not exist)"
|
||||||
|
|
||||||
|
artifacts = list(self.dist_dir.glob('*'))
|
||||||
|
if not artifacts:
|
||||||
|
return "No build artifacts found in dist/"
|
||||||
|
|
||||||
|
lines = []
|
||||||
|
for artifact in sorted(artifacts):
|
||||||
|
if artifact.is_file():
|
||||||
|
size = artifact.stat().st_size
|
||||||
|
size_kb = size / 1024
|
||||||
|
size_mb = size / (1024 * 1024)
|
||||||
|
|
||||||
|
if size_mb >= 1:
|
||||||
|
size_str = f"{size_mb:.2f} MB"
|
||||||
|
else:
|
||||||
|
size_str = f"{size_kb:.2f} KB"
|
||||||
|
|
||||||
|
lines.append(f"- **{artifact.name}** ({size_str})")
|
||||||
|
|
||||||
|
return '\n'.join(lines) if lines else "No build artifacts found"
|
||||||
|
|
||||||
|
def get_validation_results(self) -> str:
|
||||||
|
"""Get validation results summary.
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
Formatted validation results
|
||||||
|
"""
|
||||||
|
# Import here to avoid circular dependency
|
||||||
|
from ..utils.validation import ReleaseValidator
|
||||||
|
|
||||||
|
validator = ReleaseValidator(self.project_root)
|
||||||
|
is_valid, issues = validator.validate_release_state(force=True) # Force to get all issues
|
||||||
|
|
||||||
|
if is_valid:
|
||||||
|
return "✅ All validation checks passed"
|
||||||
|
else:
|
||||||
|
lines = ["Validation Issues:"]
|
||||||
|
for issue in issues:
|
||||||
|
lines.append(f"- {issue}")
|
||||||
|
return '\n'.join(lines)
|
||||||
|
|
||||||
|
def _get_project_name(self) -> str:
|
||||||
|
"""Get project name from pyproject.toml.
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
Project name or default
|
||||||
|
"""
|
||||||
|
pyproject_path = self.project_root / 'pyproject.toml'
|
||||||
|
|
||||||
|
if not pyproject_path.exists():
|
||||||
|
return "Project"
|
||||||
|
|
||||||
|
try:
|
||||||
|
import tomllib
|
||||||
|
except ImportError:
|
||||||
|
try:
|
||||||
|
import tomli as tomllib
|
||||||
|
except ImportError:
|
||||||
|
return "Project"
|
||||||
|
|
||||||
|
try:
|
||||||
|
with open(pyproject_path, 'rb') as f:
|
||||||
|
config = tomllib.load(f)
|
||||||
|
return config.get('project', {}).get('name', 'Project').title()
|
||||||
|
except Exception:
|
||||||
|
return "Project"
|
||||||
@@ -4,6 +4,7 @@ Release validation utilities.
|
|||||||
This module provides validation functions for release readiness.
|
This module provides validation functions for release readiness.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
|
import subprocess
|
||||||
from pathlib import Path
|
from pathlib import Path
|
||||||
from typing import List, Tuple, Optional
|
from typing import List, Tuple, Optional
|
||||||
|
|
||||||
@@ -48,6 +49,10 @@ class ReleaseValidator:
|
|||||||
config_issues = self._validate_configuration()
|
config_issues = self._validate_configuration()
|
||||||
issues.extend(config_issues)
|
issues.extend(config_issues)
|
||||||
|
|
||||||
|
# CHANGELOG validation
|
||||||
|
changelog_issues = self._validate_changelog()
|
||||||
|
issues.extend(changelog_issues)
|
||||||
|
|
||||||
return len(issues) == 0, issues
|
return len(issues) == 0, issues
|
||||||
|
|
||||||
def _validate_git_state(self) -> List[str]:
|
def _validate_git_state(self) -> List[str]:
|
||||||
@@ -186,6 +191,117 @@ class ReleaseValidator:
|
|||||||
|
|
||||||
return len(issues) == 0, issues
|
return len(issues) == 0, issues
|
||||||
|
|
||||||
|
def _validate_changelog(self) -> List[str]:
|
||||||
|
"""Validate CHANGELOG.md using changelog schema.
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
List of CHANGELOG-related issues
|
||||||
|
"""
|
||||||
|
issues = []
|
||||||
|
changelog_path = self.project_root / 'CHANGELOG.md'
|
||||||
|
|
||||||
|
# Check if CHANGELOG exists
|
||||||
|
if not changelog_path.exists():
|
||||||
|
issues.append("Missing CHANGELOG.md file")
|
||||||
|
return issues
|
||||||
|
|
||||||
|
# Check if changelog schema exists
|
||||||
|
schema_path = self.project_root / 'markitect' / 'schemas' / 'changelog-schema-v1.0.md'
|
||||||
|
if not schema_path.exists():
|
||||||
|
# Schema doesn't exist, skip validation
|
||||||
|
return issues
|
||||||
|
|
||||||
|
# Validate CHANGELOG with schema using markitect validate command
|
||||||
|
try:
|
||||||
|
result = subprocess.run(
|
||||||
|
[
|
||||||
|
'markitect', 'validate', str(changelog_path),
|
||||||
|
'--schema', str(schema_path),
|
||||||
|
'--semantic'
|
||||||
|
],
|
||||||
|
capture_output=True,
|
||||||
|
text=True,
|
||||||
|
cwd=self.project_root
|
||||||
|
)
|
||||||
|
|
||||||
|
if result.returncode != 0:
|
||||||
|
issues.append("CHANGELOG.md validation failed against schema")
|
||||||
|
# Parse output for specific errors
|
||||||
|
if 'Unreleased section' in result.stdout:
|
||||||
|
issues.append(" - Missing [Unreleased] section in CHANGELOG")
|
||||||
|
if 'version format' in result.stdout.lower():
|
||||||
|
issues.append(" - Invalid version format in CHANGELOG")
|
||||||
|
|
||||||
|
except FileNotFoundError:
|
||||||
|
# markitect command not available
|
||||||
|
issues.append("Cannot validate CHANGELOG (markitect command not found)")
|
||||||
|
except Exception as e:
|
||||||
|
issues.append(f"Error validating CHANGELOG: {e}")
|
||||||
|
|
||||||
|
return issues
|
||||||
|
|
||||||
|
def validate_changelog_version(self, version: str) -> Tuple[bool, List[str]]:
|
||||||
|
"""Validate that CHANGELOG has section for specified version.
|
||||||
|
|
||||||
|
Args:
|
||||||
|
version: Version to check (e.g., "0.10.0")
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
Tuple of (is_valid, list_of_issues)
|
||||||
|
"""
|
||||||
|
issues = []
|
||||||
|
changelog_path = self.project_root / 'CHANGELOG.md'
|
||||||
|
|
||||||
|
if not changelog_path.exists():
|
||||||
|
issues.append("CHANGELOG.md not found")
|
||||||
|
return False, issues
|
||||||
|
|
||||||
|
try:
|
||||||
|
content = changelog_path.read_text()
|
||||||
|
|
||||||
|
# Check for version section
|
||||||
|
version_header = f"## [{version}]"
|
||||||
|
if version_header not in content:
|
||||||
|
issues.append(f"CHANGELOG missing section for version {version}")
|
||||||
|
|
||||||
|
# Check for Unreleased section
|
||||||
|
if "## [Unreleased]" not in content:
|
||||||
|
issues.append("CHANGELOG missing [Unreleased] section")
|
||||||
|
|
||||||
|
# Check if version section has a date
|
||||||
|
import re
|
||||||
|
date_pattern = rf"## \[{re.escape(version)}\] - \d{{4}}-\d{{2}}-\d{{2}}"
|
||||||
|
if not re.search(date_pattern, content):
|
||||||
|
issues.append(f"Version {version} section missing date or has invalid date format")
|
||||||
|
|
||||||
|
except Exception as e:
|
||||||
|
issues.append(f"Error reading CHANGELOG: {e}")
|
||||||
|
|
||||||
|
return len(issues) == 0, issues
|
||||||
|
|
||||||
|
def check_version_tag_consistency(self, version: str) -> Tuple[bool, List[str]]:
|
||||||
|
"""Check consistency between CHANGELOG version and git tags.
|
||||||
|
|
||||||
|
Args:
|
||||||
|
version: Version to check (e.g., "0.10.0")
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
Tuple of (is_consistent, list_of_issues)
|
||||||
|
"""
|
||||||
|
issues = []
|
||||||
|
|
||||||
|
# Check CHANGELOG has the version
|
||||||
|
changelog_valid, changelog_issues = self.validate_changelog_version(version)
|
||||||
|
if not changelog_valid:
|
||||||
|
issues.extend(changelog_issues)
|
||||||
|
|
||||||
|
# Check git tag exists
|
||||||
|
tag_name = version if version.startswith('v') else f'v{version}'
|
||||||
|
if not self.git_manager.tag_exists(tag_name):
|
||||||
|
issues.append(f"Git tag {tag_name} doesn't exist for version in CHANGELOG")
|
||||||
|
|
||||||
|
return len(issues) == 0, issues
|
||||||
|
|
||||||
def get_validation_summary(self) -> dict:
|
def get_validation_summary(self) -> dict:
|
||||||
"""Get a comprehensive validation summary.
|
"""Get a comprehensive validation summary.
|
||||||
|
|
||||||
@@ -224,6 +340,10 @@ class ReleaseValidator:
|
|||||||
if any('authentication' in issue.lower() for issue in issues):
|
if any('authentication' in issue.lower() for issue in issues):
|
||||||
recommendations.append("Set up authentication tokens for package publishing")
|
recommendations.append("Set up authentication tokens for package publishing")
|
||||||
|
|
||||||
|
if any('CHANGELOG' in issue for issue in issues):
|
||||||
|
recommendations.append("Fix CHANGELOG.md format and ensure [Unreleased] section exists")
|
||||||
|
recommendations.append("Validate with: markitect validate CHANGELOG.md --schema changelog-schema-v1.0.md --semantic")
|
||||||
|
|
||||||
if not issues:
|
if not issues:
|
||||||
recommendations.append("Repository is ready for release!")
|
recommendations.append("Repository is ready for release!")
|
||||||
|
|
||||||
|
|||||||
548
docs/SCHEMA_MANAGEMENT_GUIDE.md
Normal file
548
docs/SCHEMA_MANAGEMENT_GUIDE.md
Normal file
@@ -0,0 +1,548 @@
|
|||||||
|
# Schema Management Guide
|
||||||
|
|
||||||
|
Complete guide to managing schemas in MarkiTect using the Schema-of-Schemas system.
|
||||||
|
|
||||||
|
## Overview
|
||||||
|
|
||||||
|
MarkiTect provides a comprehensive schema management system with:
|
||||||
|
- Markdown-first schema format with embedded JSON
|
||||||
|
- Strict naming conventions for consistency
|
||||||
|
- Metaschema validation for all schemas
|
||||||
|
- Multi-schema batch validation
|
||||||
|
- Schema registry with version tracking
|
||||||
|
|
||||||
|
## Quick Start
|
||||||
|
|
||||||
|
### 1. Create a New Schema
|
||||||
|
|
||||||
|
Create a markdown file following the naming convention: `{domain}-schema-v{major}.{minor}.md`
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# Example: blog-post-schema-v1.0.md
|
||||||
|
```
|
||||||
|
|
||||||
|
**Template:**
|
||||||
|
|
||||||
|
```markdown
|
||||||
|
---
|
||||||
|
schema-id: https://markitect.dev/schemas/blog-post/v1.0
|
||||||
|
version: 1.0.0
|
||||||
|
status: stable
|
||||||
|
domain: blog-post
|
||||||
|
description: Schema for blog post documents
|
||||||
|
---
|
||||||
|
|
||||||
|
# Blog Post Schema v1.0.0
|
||||||
|
|
||||||
|
## Overview
|
||||||
|
This schema validates blog post documents with frontmatter and content sections.
|
||||||
|
|
||||||
|
## Schema Definition
|
||||||
|
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"$schema": "http://json-schema.org/draft-07/schema#",
|
||||||
|
"$id": "https://markitect.dev/schemas/blog-post/v1.0",
|
||||||
|
"title": "Blog Post Schema",
|
||||||
|
"description": "Schema for blog post documents",
|
||||||
|
"version": "1.0.0",
|
||||||
|
"type": "object",
|
||||||
|
"properties": {
|
||||||
|
"title": {
|
||||||
|
"type": "string",
|
||||||
|
"minLength": 1
|
||||||
|
},
|
||||||
|
"author": {
|
||||||
|
"type": "string"
|
||||||
|
},
|
||||||
|
"date": {
|
||||||
|
"type": "string",
|
||||||
|
"format": "date"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"required": ["title", "author"]
|
||||||
|
}
|
||||||
|
```
|
||||||
|
\`\`\`
|
||||||
|
|
||||||
|
### 2. Validate Your Schema
|
||||||
|
|
||||||
|
Validate against the metaschema to ensure it follows MarkiTect conventions:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# Validate a single schema file
|
||||||
|
markitect schema-validate ./blog-post-schema-v1.0.md
|
||||||
|
|
||||||
|
# See detailed errors
|
||||||
|
markitect schema-validate ./blog-post-schema-v1.0.md --detailed-errors
|
||||||
|
```
|
||||||
|
|
||||||
|
### 3. Ingest into Registry
|
||||||
|
|
||||||
|
Add your schema to the registry:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
markitect schema-ingest blog-post-schema-v1.0.md
|
||||||
|
```
|
||||||
|
|
||||||
|
### 4. List Registered Schemas
|
||||||
|
|
||||||
|
View all schemas with numbered references:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# Simple format (default)
|
||||||
|
markitect schema-list
|
||||||
|
|
||||||
|
# Table format
|
||||||
|
markitect schema-list --format table
|
||||||
|
|
||||||
|
# JSON format
|
||||||
|
markitect schema-list --format json
|
||||||
|
```
|
||||||
|
|
||||||
|
**Output:**
|
||||||
|
```
|
||||||
|
Found 4 schema(s):
|
||||||
|
|
||||||
|
[1] 🔧 blog-post-schema-v1.0.md (added: 2026-01-05T10:30:00)
|
||||||
|
[2] 🔧 schema-schema-v1.0.md (added: 2026-01-05T03:33:42)
|
||||||
|
[3] 🔧 manpage-schema-v1.0.md (added: 2026-01-05T03:33:42)
|
||||||
|
[4] 🔧 api-documentation-schema-v1.0.md (added: 2026-01-05T03:33:35)
|
||||||
|
```
|
||||||
|
|
||||||
|
## Schema Validation
|
||||||
|
|
||||||
|
### Single Schema Validation
|
||||||
|
|
||||||
|
**By number:**
|
||||||
|
```bash
|
||||||
|
markitect schema-validate 1
|
||||||
|
```
|
||||||
|
|
||||||
|
**By filename (from registry):**
|
||||||
|
```bash
|
||||||
|
markitect schema-validate blog-post-schema-v1.0.md
|
||||||
|
```
|
||||||
|
|
||||||
|
**By filesystem path:**
|
||||||
|
```bash
|
||||||
|
markitect schema-validate ./my-schema.md
|
||||||
|
```
|
||||||
|
|
||||||
|
### Batch Validation
|
||||||
|
|
||||||
|
**Validate a range:**
|
||||||
|
```bash
|
||||||
|
markitect schema-validate 1-3
|
||||||
|
```
|
||||||
|
|
||||||
|
**Validate specific schemas:**
|
||||||
|
```bash
|
||||||
|
markitect schema-validate 1,3,5
|
||||||
|
```
|
||||||
|
|
||||||
|
**Validate all schemas:**
|
||||||
|
```bash
|
||||||
|
markitect schema-validate --all
|
||||||
|
```
|
||||||
|
|
||||||
|
**Output:**
|
||||||
|
```
|
||||||
|
Validating 4 schema(s)...
|
||||||
|
|
||||||
|
Results:
|
||||||
|
|
||||||
|
# Schema Status Details
|
||||||
|
--- -------------------------------- -------- ---------
|
||||||
|
1 blog-post-schema-v1.0.md ✅ Valid v1.0.0
|
||||||
|
2 schema-schema-v1.0.md ✅ Valid v1.0.0
|
||||||
|
3 manpage-schema-v1.0.md ✅ Valid v1.0.0
|
||||||
|
4 api-documentation-schema-v1.0.md ✅ Valid v1.0.0
|
||||||
|
|
||||||
|
Summary: 4 valid, 0 failed
|
||||||
|
```
|
||||||
|
|
||||||
|
## Document Validation (Semantic)
|
||||||
|
|
||||||
|
### Validate Documents Against Schemas
|
||||||
|
|
||||||
|
Beyond validating schema structure, MarkiTect can validate actual markdown documents against schemas, checking both structural (AST) and semantic (x-markitect extensions) aspects.
|
||||||
|
|
||||||
|
**Validate a document:**
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# Full validation (structural + semantic)
|
||||||
|
markitect validate my-document.md --schema manpage-schema-v1.0.md
|
||||||
|
|
||||||
|
# Only structural validation (classic mode)
|
||||||
|
markitect validate my-document.md --schema schema.json --no-semantic
|
||||||
|
|
||||||
|
# With external link checking (may be slow)
|
||||||
|
markitect validate my-document.md --schema manpage-schema-v1.0.md --check-links
|
||||||
|
|
||||||
|
# Strict mode (warnings become errors)
|
||||||
|
markitect validate my-document.md --schema manpage-schema-v1.0.md --strict
|
||||||
|
```
|
||||||
|
|
||||||
|
### What is Validated
|
||||||
|
|
||||||
|
**Structural Validation** (always enabled):
|
||||||
|
- Document AST structure matches JSON Schema properties
|
||||||
|
- Heading counts, paragraph counts, code block counts
|
||||||
|
- Element types and nesting
|
||||||
|
|
||||||
|
**Semantic Validation** (enabled by default with --semantic):
|
||||||
|
- **Section Classifications**: Checks that documents have required sections, don't have improper sections
|
||||||
|
- REQUIRED sections must be present (ERROR if missing)
|
||||||
|
- RECOMMENDED sections should be present (WARNING if missing)
|
||||||
|
- IMPROPER sections must not be present (ERROR if found)
|
||||||
|
- DISCOURAGED sections should not be present (WARNING if found)
|
||||||
|
- OPTIONAL sections may or may not be present (no check)
|
||||||
|
- **Content Patterns**: Validates content matches regex patterns
|
||||||
|
- `required_patterns`: Content must match (ERROR if missing)
|
||||||
|
- `forbidden_patterns`: Content must not match (ERROR if found)
|
||||||
|
- `discouraged_patterns`: Content should not match (WARNING if found)
|
||||||
|
- **Quality Metrics**: Checks word counts, sentence counts
|
||||||
|
- `min_words`, `max_words`: Word count requirements (WARNING)
|
||||||
|
- `min_sentences`: Minimum sentence count (WARNING)
|
||||||
|
- **Link Validation**: Validates internal and external links (optional)
|
||||||
|
- Internal links: Checked by default when semantic validation enabled
|
||||||
|
- Fragment links (#section-name) verified to exist (ERROR if broken)
|
||||||
|
- Relative file paths checked for existence (ERROR if broken)
|
||||||
|
- External links: Opt-in with --check-links flag (may be slow)
|
||||||
|
- HTTP/HTTPS URLs validated with HEAD requests (WARNING if broken)
|
||||||
|
- Email validation: Validates mailto: link format (WARNING if invalid)
|
||||||
|
- Fragment policy: Configurable allow/disallow fragment identifiers
|
||||||
|
|
||||||
|
### Validation Output
|
||||||
|
|
||||||
|
```
|
||||||
|
Validation result: VALID
|
||||||
|
File: my-command.1.md
|
||||||
|
Schema: schema file: manpage-schema-v1.0.md
|
||||||
|
✅ Document structure matches schema requirements
|
||||||
|
|
||||||
|
============================================================
|
||||||
|
Semantic Validation Results:
|
||||||
|
============================================================
|
||||||
|
Section Validation:
|
||||||
|
✅ SYNOPSIS - Present (required)
|
||||||
|
✅ DESCRIPTION - Present (required)
|
||||||
|
✅ EXAMPLES - Present (recommended)
|
||||||
|
|
||||||
|
Content Validation:
|
||||||
|
✅ All content requirements met
|
||||||
|
|
||||||
|
Link Validation:
|
||||||
|
✅ All 12 links valid
|
||||||
|
|
||||||
|
Summary:
|
||||||
|
Sections checked: 3
|
||||||
|
Sections found: 5
|
||||||
|
Errors: 0
|
||||||
|
Warnings: 0
|
||||||
|
Status: PASSED ✅
|
||||||
|
```
|
||||||
|
|
||||||
|
### Common Validation Scenarios
|
||||||
|
|
||||||
|
**Example 1: Missing Required Section**
|
||||||
|
```bash
|
||||||
|
$ markitect validate doc.md --schema manpage-schema-v1.0.md
|
||||||
|
❌ Document validation failed
|
||||||
|
|
||||||
|
Section Validation:
|
||||||
|
❌ SYNOPSIS - SYNOPSIS section is mandatory
|
||||||
|
✅ DESCRIPTION - Present (required)
|
||||||
|
|
||||||
|
Errors: 1
|
||||||
|
Status: FAILED ❌
|
||||||
|
```
|
||||||
|
|
||||||
|
**Example 2: Forbidden Pattern Found**
|
||||||
|
```bash
|
||||||
|
$ markitect validate doc.md --schema manpage-schema-v1.0.md
|
||||||
|
|
||||||
|
Content Validation:
|
||||||
|
❌ SYNOPSIS - Forbidden pattern found: 'TODO'
|
||||||
|
|
||||||
|
Errors: 1
|
||||||
|
Status: FAILED ❌
|
||||||
|
```
|
||||||
|
|
||||||
|
**Example 3: Content Too Short (Warning)**
|
||||||
|
```bash
|
||||||
|
$ markitect validate doc.md --schema manpage-schema-v1.0.md
|
||||||
|
|
||||||
|
Content Validation:
|
||||||
|
⚠️ DESCRIPTION - Content too short (25 words, minimum 50)
|
||||||
|
|
||||||
|
Warnings: 1
|
||||||
|
Status: PASSED ✅
|
||||||
|
|
||||||
|
# With --strict flag, this would fail:
|
||||||
|
$ markitect validate doc.md --schema manpage-schema-v1.0.md --strict
|
||||||
|
Status: FAILED ❌ (warnings treated as errors)
|
||||||
|
```
|
||||||
|
|
||||||
|
**Example 4: Broken Internal Link**
|
||||||
|
```bash
|
||||||
|
$ markitect validate doc.md --schema manpage-schema-v1.0.md
|
||||||
|
|
||||||
|
Link Validation:
|
||||||
|
❌ #nonexistent-section - Internal link target not found: #nonexistent-section
|
||||||
|
|
||||||
|
Errors: 1
|
||||||
|
Status: FAILED ❌
|
||||||
|
```
|
||||||
|
|
||||||
|
**Example 5: External Link Validation**
|
||||||
|
```bash
|
||||||
|
# Enable external link checking (may be slow)
|
||||||
|
$ markitect validate doc.md --schema manpage-schema-v1.0.md --check-links
|
||||||
|
|
||||||
|
Link Validation:
|
||||||
|
✅ http://example.com - Valid
|
||||||
|
⚠️ http://broken-link.invalid - External link unreachable: Name or service not known
|
||||||
|
|
||||||
|
Warnings: 1
|
||||||
|
Status: PASSED ✅
|
||||||
|
```
|
||||||
|
|
||||||
|
## Schema Naming Conventions
|
||||||
|
|
||||||
|
All schema filenames must follow this pattern:
|
||||||
|
|
||||||
|
```
|
||||||
|
{domain}-schema-v{major}.{minor}.md
|
||||||
|
```
|
||||||
|
|
||||||
|
### Rules
|
||||||
|
|
||||||
|
- **Domain**: Lowercase letters, numbers, and hyphens only
|
||||||
|
- **Version**: Major.minor format (e.g., `v1.0`, `v2.3`)
|
||||||
|
- **Extension**: Must be `.md`
|
||||||
|
- **No spaces**: Use hyphens for separation
|
||||||
|
|
||||||
|
### Valid Examples
|
||||||
|
|
||||||
|
- `blog-post-schema-v1.0.md`
|
||||||
|
- `api-documentation-schema-v2.1.md`
|
||||||
|
- `user-profile-schema-v1.0.md`
|
||||||
|
|
||||||
|
### Invalid Examples
|
||||||
|
|
||||||
|
- `BlogPost-schema-v1.0.md` (uppercase)
|
||||||
|
- `blog_post-schema-v1.0.md` (underscore)
|
||||||
|
- `blog-post-v1.0.md` (missing "schema")
|
||||||
|
- `blog-post-schema-v1.md` (missing minor version)
|
||||||
|
|
||||||
|
## Required Schema Fields
|
||||||
|
|
||||||
|
All schemas must include these fields:
|
||||||
|
|
||||||
|
### Frontmatter (YAML)
|
||||||
|
```yaml
|
||||||
|
---
|
||||||
|
schema-id: https://markitect.dev/schemas/{domain}/v{major}.{minor}
|
||||||
|
version: {major}.{minor}.{patch}
|
||||||
|
status: draft|stable|deprecated
|
||||||
|
domain: {domain}
|
||||||
|
description: Brief description
|
||||||
|
---
|
||||||
|
```
|
||||||
|
|
||||||
|
### JSON Schema
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"$schema": "http://json-schema.org/draft-07/schema#",
|
||||||
|
"$id": "https://markitect.dev/schemas/{domain}/v{major}.{minor}",
|
||||||
|
"title": "Schema Title",
|
||||||
|
"description": "Schema description",
|
||||||
|
"version": "{major}.{minor}.{patch}"
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
## Common Workflows
|
||||||
|
|
||||||
|
### Revalidate All Schemas After Metaschema Changes
|
||||||
|
|
||||||
|
When you update the metaschema, revalidate all registered schemas:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
markitect schema-validate --all
|
||||||
|
```
|
||||||
|
|
||||||
|
### Check Schema Rigidity
|
||||||
|
|
||||||
|
Analyze a schema for overly rigid constraints:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
markitect schema-analyze my-schema.md
|
||||||
|
```
|
||||||
|
|
||||||
|
### Refine a Rigid Schema
|
||||||
|
|
||||||
|
Automatically loosen overly specific constraints:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# Dry run (preview changes)
|
||||||
|
markitect schema-refine my-schema.md --dry-run
|
||||||
|
|
||||||
|
# Apply changes
|
||||||
|
markitect schema-refine my-schema.md
|
||||||
|
|
||||||
|
# Interactive mode
|
||||||
|
markitect schema-refine my-schema.md --interactive
|
||||||
|
```
|
||||||
|
|
||||||
|
### Get Schema Details
|
||||||
|
|
||||||
|
View schema metadata:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
markitect schema-get blog-post-schema-v1.0.md
|
||||||
|
```
|
||||||
|
|
||||||
|
### Delete a Schema
|
||||||
|
|
||||||
|
Remove a schema from the registry:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
markitect schema-delete blog-post-schema-v1.0.md --confirm
|
||||||
|
```
|
||||||
|
|
||||||
|
## Resolution Precedence
|
||||||
|
|
||||||
|
When validating schemas, MarkiTect uses this resolution order:
|
||||||
|
|
||||||
|
1. **Registry (by filename)**: Exact match in the database
|
||||||
|
2. **Filesystem (fallback)**: If not found in registry or looks like a path
|
||||||
|
|
||||||
|
### Examples
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# Looks up in registry first
|
||||||
|
markitect schema-validate blog-post-schema-v1.0.md
|
||||||
|
|
||||||
|
# Forces filesystem lookup (contains /)
|
||||||
|
markitect schema-validate ./blog-post-schema-v1.0.md
|
||||||
|
|
||||||
|
# Also forces filesystem
|
||||||
|
markitect schema-validate ../schemas/blog-post-schema-v1.0.md
|
||||||
|
```
|
||||||
|
|
||||||
|
## Best Practices
|
||||||
|
|
||||||
|
### Schema Development
|
||||||
|
|
||||||
|
1. **Start with a template**: Use an existing schema as a starting point
|
||||||
|
2. **Validate early**: Validate against the metaschema before ingesting
|
||||||
|
3. **Use semantic versioning**: Major.minor.patch for all versions
|
||||||
|
4. **Document thoroughly**: Include overview, usage, and examples
|
||||||
|
5. **Test with real documents**: Validate actual documents against your schema
|
||||||
|
|
||||||
|
### Version Management
|
||||||
|
|
||||||
|
- **Increment major version**: Breaking changes to schema structure
|
||||||
|
- **Increment minor version**: Backward-compatible additions
|
||||||
|
- **Increment patch version**: Bug fixes and clarifications
|
||||||
|
|
||||||
|
### Schema Organization
|
||||||
|
|
||||||
|
```
|
||||||
|
markitect/schemas/
|
||||||
|
├── schema-schema-v1.0.md # Metaschema
|
||||||
|
├── manpage-schema-v1.0.md # Man page documents
|
||||||
|
├── api-documentation-schema-v1.0.md
|
||||||
|
├── terminology-schema-v1.0.md
|
||||||
|
└── blog-post-schema-v1.0.md # Your schemas
|
||||||
|
```
|
||||||
|
|
||||||
|
## Troubleshooting
|
||||||
|
|
||||||
|
### Schema Not Found
|
||||||
|
|
||||||
|
```
|
||||||
|
❌ Schema 'my-schema.md' not found in registry or filesystem
|
||||||
|
```
|
||||||
|
|
||||||
|
**Solution:** Use `markitect schema-list` to see available schemas, or provide a path: `./my-schema.md`
|
||||||
|
|
||||||
|
### Validation Fails
|
||||||
|
|
||||||
|
```
|
||||||
|
❌ Schema validation failed: my-schema.md
|
||||||
|
Found 2 validation error(s):
|
||||||
|
```
|
||||||
|
|
||||||
|
**Solution:** Check error messages and compare with metaschema requirements. Use `--detailed-errors` for more context.
|
||||||
|
|
||||||
|
### Invalid Selector
|
||||||
|
|
||||||
|
```
|
||||||
|
❌ Invalid selector: Range 1-10 is out of bounds. Valid range: 1-4
|
||||||
|
```
|
||||||
|
|
||||||
|
**Solution:** Use `markitect schema-list` to see valid numbers, or check your range syntax.
|
||||||
|
|
||||||
|
## Advanced Usage
|
||||||
|
|
||||||
|
### Scripting with Schema Commands
|
||||||
|
|
||||||
|
Validate schemas in CI/CD:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
#!/bin/bash
|
||||||
|
# Validate all schemas and exit with error if any fail
|
||||||
|
if ! markitect schema-validate --all; then
|
||||||
|
echo "Schema validation failed!"
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
echo "All schemas valid"
|
||||||
|
```
|
||||||
|
|
||||||
|
### Batch Operations
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# Validate recently added schemas
|
||||||
|
markitect schema-validate 1-3
|
||||||
|
|
||||||
|
# Validate specific critical schemas
|
||||||
|
markitect schema-validate 1,5,8
|
||||||
|
|
||||||
|
# Check just the metaschema
|
||||||
|
markitect schema-validate 2
|
||||||
|
```
|
||||||
|
|
||||||
|
## Schema Extensions
|
||||||
|
|
||||||
|
MarkiTect supports custom extensions in schemas:
|
||||||
|
|
||||||
|
- `x-markitect-sections`: Section classification (required, recommended, optional, discouraged, improper)
|
||||||
|
- `x-markitect-content-control`: Content validation rules and patterns
|
||||||
|
- `x-markitect-metadata`: Additional metadata for MarkiTect processing
|
||||||
|
|
||||||
|
See existing schemas for examples of these extensions.
|
||||||
|
|
||||||
|
## Future Enhancements
|
||||||
|
|
||||||
|
Planned features:
|
||||||
|
- Wildcard/globbing support: `markitect schema-validate */manpage*`
|
||||||
|
- Schema diff tool: Compare schema versions
|
||||||
|
- Schema migration assistant: Help upgrade documents to new schema versions
|
||||||
|
|
||||||
|
## Related Documentation
|
||||||
|
|
||||||
|
- [Schema Naming Specification](../history/260105-schema-of-schemas/SCHEMA_NAMING_SPEC.md)
|
||||||
|
- [Schema Loader Guide](../history/260105-schema-of-schemas/SCHEMA_LOADER_GUIDE.md)
|
||||||
|
- [Metaschema Reference](../markitect/schemas/schema-schema-v1.0.md)
|
||||||
|
- [Implementation Workplan](../history/260105-schema-of-schemas/WORKPLAN.md) (archived)
|
||||||
|
|
||||||
|
## Support
|
||||||
|
|
||||||
|
For issues or questions:
|
||||||
|
- Check existing schemas as examples
|
||||||
|
- Review metaschema validation errors carefully
|
||||||
|
- Use `--detailed-errors` for more context
|
||||||
|
- Consult the metaschema for requirements
|
||||||
@@ -13,9 +13,14 @@ This example showcases the "dogfooding" principle - using MarkiTect's schema val
|
|||||||
|
|
||||||
## Files in This Example
|
## Files in This Example
|
||||||
|
|
||||||
### `markdown-manpage-schema.json`
|
### Schema File
|
||||||
|
|
||||||
A JSON Schema defining the structure of Unix-style manual pages written in Markdown.
|
The manpage schema is now managed in MarkiTect's schema registry:
|
||||||
|
- **Schema Name**: `manpage-schema-v1.0.md`
|
||||||
|
- **Location**: `markitect/schemas/manpage-schema-v1.0.md`
|
||||||
|
- **Format**: Markdown with embedded JSON (following schema-of-schemas standard)
|
||||||
|
|
||||||
|
This schema defines the structure of Unix-style manual pages written in Markdown.
|
||||||
|
|
||||||
**Key Features:**
|
**Key Features:**
|
||||||
- Validates H1 title format: `command(section) - description`
|
- Validates H1 title format: `command(section) - description`
|
||||||
@@ -58,63 +63,68 @@ A comprehensive manual page (section 7) documenting MarkiTect's markdown schema
|
|||||||
|
|
||||||
## Running the Example
|
## Running the Example
|
||||||
|
|
||||||
### 1. Validate the Manual Against the Schema
|
### 1. List Available Schemas
|
||||||
|
|
||||||
Verify that the manual conforms to the manpage schema:
|
View all registered schemas (including the manpage schema):
|
||||||
|
|
||||||
|
```bash
|
||||||
|
markitect schema-list
|
||||||
|
```
|
||||||
|
|
||||||
|
You should see `manpage-schema-v1.0.md` listed with a number.
|
||||||
|
|
||||||
|
### 2. Validate the Manual Against the Schema
|
||||||
|
|
||||||
|
Verify that the manual conforms to the manpage schema using the new multi-schema validation:
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
cd examples/manpages
|
cd examples/manpages
|
||||||
|
|
||||||
|
# Validate by schema filename (from registry)
|
||||||
|
markitect schema-validate manpage-schema-v1.0.md
|
||||||
|
|
||||||
|
# Or validate by number (if schema is #2 in the list)
|
||||||
|
markitect schema-validate 2
|
||||||
|
|
||||||
|
# Or validate a specific document file
|
||||||
markitect validate markdown-schema-validation.1.md \
|
markitect validate markdown-schema-validation.1.md \
|
||||||
--schema markdown-manpage-schema.json
|
--schema manpage-schema-v1.0.md
|
||||||
```
|
```
|
||||||
|
|
||||||
Expected output: ✅ **VALID** - Document structure matches schema requirements
|
Expected output: ✅ **VALID** - Document structure matches schema requirements
|
||||||
|
|
||||||
### 2. Show Detailed Validation
|
### 3. Show Detailed Validation
|
||||||
|
|
||||||
See detailed validation information:
|
See detailed validation information:
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
markitect validate markdown-schema-validation.1.md \
|
markitect schema-validate manpage-schema-v1.0.md --detailed-errors
|
||||||
--schema markdown-manpage-schema.json \
|
|
||||||
--detailed-errors
|
|
||||||
```
|
```
|
||||||
|
|
||||||
### 3. Generate Schema from the Manual
|
### 4. Validate Multiple Schemas
|
||||||
|
|
||||||
Analyze the manual's actual structure:
|
Validate all registered schemas at once:
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
markitect schema-generate markdown-schema-validation.1.md \
|
# Validate all schemas
|
||||||
--output actual-structure-schema.json
|
markitect schema-validate --all
|
||||||
|
|
||||||
cat actual-structure-schema.json
|
# Validate a range of schemas
|
||||||
|
markitect schema-validate 1-3
|
||||||
|
|
||||||
|
# Validate specific schemas
|
||||||
|
markitect schema-validate 1,3,5
|
||||||
```
|
```
|
||||||
|
|
||||||
Compare the generated schema with the manpage schema to see how the manual conforms.
|
### 5. Examine the Schema
|
||||||
|
|
||||||
### 4. Examine AST Structure
|
View the manpage schema in markdown format:
|
||||||
|
|
||||||
View the parsed structure of the manual:
|
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
markitect ast-show markdown-schema-validation.1.md --format tree
|
markitect schema-get manpage-schema-v1.0.md
|
||||||
```
|
|
||||||
|
|
||||||
Or in compact format:
|
# Or view the file directly
|
||||||
|
cat ../../markitect/schemas/manpage-schema-v1.0.md
|
||||||
```bash
|
|
||||||
markitect ast-show markdown-schema-validation.1.md --format compact | head -50
|
|
||||||
```
|
|
||||||
|
|
||||||
### 5. Store Schema for Reuse
|
|
||||||
|
|
||||||
Add the manpage schema to MarkiTect's database:
|
|
||||||
|
|
||||||
```bash
|
|
||||||
markitect schema-ingest markdown-manpage-schema.json
|
|
||||||
markitect schema-list
|
|
||||||
```
|
```
|
||||||
|
|
||||||
### 6. Validate Other Manpages
|
### 6. Validate Other Manpages
|
||||||
@@ -122,22 +132,9 @@ markitect schema-list
|
|||||||
Use the schema to validate other manual pages in the project:
|
Use the schema to validate other manual pages in the project:
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
|
# Validate using schema name from registry
|
||||||
markitect validate ../../docs/manuals/markitect.1.md \
|
markitect validate ../../docs/manuals/markitect.1.md \
|
||||||
--schema markdown-manpage-schema.json
|
--schema manpage-schema-v1.0.md
|
||||||
|
|
||||||
markitect validate ../../docs/manuals/issue.1.md \
|
|
||||||
--schema markdown-manpage-schema.json
|
|
||||||
```
|
|
||||||
|
|
||||||
### 7. Generate Manpage Template
|
|
||||||
|
|
||||||
Create a template for new manpages:
|
|
||||||
|
|
||||||
```bash
|
|
||||||
markitect generate-stub markdown-manpage-schema.json \
|
|
||||||
--output new-manpage-template.md
|
|
||||||
|
|
||||||
cat new-manpage-template.md
|
|
||||||
```
|
```
|
||||||
|
|
||||||
## What This Example Demonstrates
|
## What This Example Demonstrates
|
||||||
@@ -246,7 +243,7 @@ Add to `.github/workflows/docs.yml` or similar:
|
|||||||
run: |
|
run: |
|
||||||
for manpage in docs/manuals/*.md; do
|
for manpage in docs/manuals/*.md; do
|
||||||
markitect validate "$manpage" \
|
markitect validate "$manpage" \
|
||||||
--schema examples/manpages/markdown-manpage-schema.json \
|
--schema manpage-schema-v1.0.md \
|
||||||
|| exit 1
|
|| exit 1
|
||||||
done
|
done
|
||||||
```
|
```
|
||||||
@@ -261,11 +258,11 @@ changed_manpages=$(git diff --cached --name-only --diff-filter=ACM | grep 'docs/
|
|||||||
|
|
||||||
for manpage in $changed_manpages; do
|
for manpage in $changed_manpages; do
|
||||||
markitect validate "$manpage" \
|
markitect validate "$manpage" \
|
||||||
--schema examples/manpages/markdown-manpage-schema.json \
|
--schema manpage-schema-v1.0.md \
|
||||||
--quiet || {
|
--quiet || {
|
||||||
echo "Manpage validation failed: $manpage"
|
echo "Manpage validation failed: $manpage"
|
||||||
markitect validate "$manpage" \
|
markitect validate "$manpage" \
|
||||||
--schema examples/manpages/markdown-manpage-schema.json \
|
--schema manpage-schema-v1.0.md \
|
||||||
--detailed-errors
|
--detailed-errors
|
||||||
exit 1
|
exit 1
|
||||||
}
|
}
|
||||||
@@ -282,7 +279,7 @@ validate-manpages:
|
|||||||
@echo "Validating manual pages..."
|
@echo "Validating manual pages..."
|
||||||
@for manpage in docs/manuals/*.md; do \
|
@for manpage in docs/manuals/*.md; do \
|
||||||
markitect validate "$$manpage" \
|
markitect validate "$$manpage" \
|
||||||
--schema examples/manpages/markdown-manpage-schema.json \
|
--schema manpage-schema-v1.0.md \
|
||||||
|| exit 1; \
|
|| exit 1; \
|
||||||
done
|
done
|
||||||
@echo "✅ All manpages valid"
|
@echo "✅ All manpages valid"
|
||||||
@@ -326,15 +323,17 @@ Create specialized schemas for different manpage types:
|
|||||||
|
|
||||||
```bash
|
```bash
|
||||||
# For command manpages (section 1)
|
# For command manpages (section 1)
|
||||||
cp markdown-manpage-schema.json command-manpage-schema.json
|
cp markitect/schemas/manpage-schema-v1.0.md command-manpage-schema-v1.0.md
|
||||||
# Edit to require COMMANDS section
|
# Edit to require COMMANDS section
|
||||||
|
markitect schema-validate ./command-manpage-schema-v1.0.md
|
||||||
|
markitect schema-ingest ./command-manpage-schema-v1.0.md
|
||||||
|
|
||||||
# For format manpages (section 5)
|
# For format manpages (section 5)
|
||||||
cp markdown-manpage-schema.json format-manpage-schema.json
|
cp markitect/schemas/manpage-schema-v1.0.md format-manpage-schema-v1.0.md
|
||||||
# Edit to require FORMAT section
|
# Edit to require FORMAT section
|
||||||
|
|
||||||
# For convention manpages (section 7)
|
# For convention manpages (section 7)
|
||||||
cp markdown-manpage-schema.json convention-manpage-schema.json
|
cp markitect/schemas/manpage-schema-v1.0.md convention-manpage-schema-v1.0.md
|
||||||
# Edit to be more flexible
|
# Edit to be more flexible
|
||||||
```
|
```
|
||||||
|
|
||||||
@@ -343,9 +342,9 @@ cp markdown-manpage-schema.json convention-manpage-schema.json
|
|||||||
Apply the manpage schema to your project:
|
Apply the manpage schema to your project:
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
# Validate README
|
# Validate README (if it follows manpage structure)
|
||||||
markitect validate README.md \
|
markitect validate README.md \
|
||||||
--schema markdown-manpage-schema.json
|
--schema manpage-schema-v1.0.md
|
||||||
|
|
||||||
# May need adjustments for non-manpage docs
|
# May need adjustments for non-manpage docs
|
||||||
```
|
```
|
||||||
|
|||||||
41
examples/templates/adr-template.md
Normal file
41
examples/templates/adr-template.md
Normal file
@@ -0,0 +1,41 @@
|
|||||||
|
<!-- Generated from schema: markitect/schemas/adr-schema-v1.0.md -->
|
||||||
|
|
||||||
|
# Architecture Decision Record Schema with Classifications
|
||||||
|
|
||||||
|
TODO: Add content for introduction section.
|
||||||
|
|
||||||
|
## Introduction
|
||||||
|
|
||||||
|
TODO: Add content for section_level_2 section.
|
||||||
|
|
||||||
|
## Main Content
|
||||||
|
|
||||||
|
TODO: Add content for section_level_2 section.
|
||||||
|
|
||||||
|
## Conclusion
|
||||||
|
|
||||||
|
TODO: Add content for section_level_2 section.
|
||||||
|
|
||||||
|
## Summary
|
||||||
|
|
||||||
|
TODO: Add content for section_level_2 section.
|
||||||
|
|
||||||
|
## Overview
|
||||||
|
|
||||||
|
TODO: Add content for section_level_2 section.
|
||||||
|
|
||||||
|
## Section 6
|
||||||
|
|
||||||
|
TODO: Add content for section_level_2 section.
|
||||||
|
|
||||||
|
### Background
|
||||||
|
|
||||||
|
TODO: Add content for section_level_3 section.
|
||||||
|
|
||||||
|
### Analysis
|
||||||
|
|
||||||
|
TODO: Add content for section_level_3 section.
|
||||||
|
|
||||||
|
### Implementation
|
||||||
|
|
||||||
|
TODO: Add content for section_level_3 section.
|
||||||
@@ -13,9 +13,11 @@ Terminology documents (glossaries, dictionaries, lexicons) benefit from consiste
|
|||||||
## Files
|
## Files
|
||||||
|
|
||||||
- **terminology-example.md** - Example terminology document with proper structure
|
- **terminology-example.md** - Example terminology document with proper structure
|
||||||
- **terminology-schema.json** - JSON schema for validating terminology documents
|
- **Schema**: `terminology-schema-v1.0.md` (in `markitect/schemas/`)
|
||||||
- **README.md** - This file
|
- **README.md** - This file
|
||||||
|
|
||||||
|
The terminology schema now follows the schema-of-schemas standard with markdown format and embedded JSON.
|
||||||
|
|
||||||
## Terminology Document Structure
|
## Terminology Document Structure
|
||||||
|
|
||||||
A well-structured terminology document includes:
|
A well-structured terminology document includes:
|
||||||
@@ -51,39 +53,65 @@ Each term should include:
|
|||||||
|
|
||||||
## Usage
|
## Usage
|
||||||
|
|
||||||
### Using the Registered Schema
|
### List Available Schemas
|
||||||
|
|
||||||
The terminology schema is registered in markitect's database and can be used by name:
|
View all registered schemas (including terminology schema):
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
# List all registered schemas (terminology-schema.json should appear)
|
|
||||||
markitect schema-list
|
markitect schema-list
|
||||||
|
```
|
||||||
|
|
||||||
# Validate using the registered schema
|
You should see `terminology-schema-v1.0.md` listed with a number.
|
||||||
markitect validate my-glossary.md --schema terminology-schema.json
|
|
||||||
|
|
||||||
# Or use the local file directly
|
### Validate Using the Schema
|
||||||
markitect validate my-glossary.md --schema examples/terminology/terminology-schema.json
|
|
||||||
|
The terminology schema is registered in MarkiTect's database. You can validate using multiple methods:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# By schema number (if terminology schema is #4 in the list)
|
||||||
|
markitect schema-validate 4
|
||||||
|
|
||||||
|
# By schema filename (from registry)
|
||||||
|
markitect schema-validate terminology-schema-v1.0.md
|
||||||
|
|
||||||
|
# Validate a specific document file
|
||||||
|
markitect validate my-glossary.md --schema terminology-schema-v1.0.md
|
||||||
|
|
||||||
|
# Or use the local file path directly
|
||||||
|
markitect validate my-glossary.md --schema ./markitect/schemas/terminology-schema-v1.0.md
|
||||||
```
|
```
|
||||||
|
|
||||||
### Validate with Detailed Errors
|
### Validate with Detailed Errors
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
markitect validate my-glossary.md --schema terminology-schema.json --detailed-errors
|
markitect schema-validate terminology-schema-v1.0.md --detailed-errors
|
||||||
```
|
```
|
||||||
|
|
||||||
### Register the Schema (if needed)
|
### Batch Validation
|
||||||
|
|
||||||
If the schema isn't already registered, you can add it to markitect's database:
|
Validate multiple schemas at once:
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
markitect schema-ingest markitect/schemas/terminology-schema.json
|
# Validate all schemas
|
||||||
|
markitect schema-validate --all
|
||||||
|
|
||||||
|
# Validate a range
|
||||||
|
markitect schema-validate 1-4
|
||||||
|
|
||||||
|
# Validate specific schemas
|
||||||
|
markitect schema-validate 2,4
|
||||||
```
|
```
|
||||||
|
|
||||||
### Generate Schema from Example
|
### View the Schema
|
||||||
|
|
||||||
|
Examine the terminology schema:
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
markitect schema-generate terminology-example.md --output my-terminology-schema.json
|
# Get from registry
|
||||||
|
markitect schema-get terminology-schema-v1.0.md
|
||||||
|
|
||||||
|
# Or view the markdown file directly
|
||||||
|
cat markitect/schemas/terminology-schema-v1.0.md
|
||||||
```
|
```
|
||||||
|
|
||||||
## Schema Features
|
## Schema Features
|
||||||
@@ -238,7 +266,7 @@ Include specialized validation rules:
|
|||||||
```bash
|
```bash
|
||||||
#!/bin/bash
|
#!/bin/bash
|
||||||
# .git/hooks/pre-commit
|
# .git/hooks/pre-commit
|
||||||
markitect validate docs/glossary.md --schema schemas/terminology-schema.json
|
markitect validate docs/glossary.md --schema terminology-schema-v1.0.md
|
||||||
```
|
```
|
||||||
|
|
||||||
### GitHub Actions
|
### GitHub Actions
|
||||||
@@ -257,7 +285,7 @@ jobs:
|
|||||||
- name: Validate Glossary
|
- name: Validate Glossary
|
||||||
run: |
|
run: |
|
||||||
markitect validate docs/glossary.md \
|
markitect validate docs/glossary.md \
|
||||||
--schema schemas/terminology-schema.json \
|
--schema terminology-schema-v1.0.md \
|
||||||
--detailed-errors
|
--detailed-errors
|
||||||
```
|
```
|
||||||
|
|
||||||
|
|||||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user