Packages
A library for validating Markdown task lists with structured format specifications. Features: checkbox subtasks, dependencies, code quality KPIs, task categories, multi-project prefixes, and comprehensive error handling documentation.
Current section
16 Versions
Jump to
Current section
16 Versions
Compare versions
5
files changed
+1117
additions
-131
deletions
| @@ -9,7 +9,7 @@ Add `task_validator` to your list of dependencies in `mix.exs`: | |
| 9 9 | ```elixir |
| 10 10 | def deps do |
| 11 11 | [ |
| 12 | - {:task_validator, "~> 0.5.0"} |
| 12 | + {:task_validator, "~> 0.6.0"} |
| 13 13 | ] |
| 14 14 | end |
| 15 15 | ``` |
| @@ -30,6 +30,10 @@ mix create_template | |
| 30 30 | |
| 31 31 | # Create a template with custom prefix |
| 32 32 | mix create_template --prefix SSH |
| 33 | + |
| 34 | + # Create a template for a specific category |
| 35 | + mix create_template --category features |
| 36 | + mix create_template --category documentation |
| 33 37 | ``` |
| 34 38 | |
| 35 39 | ### Programmatic |
| @@ -48,17 +52,26 @@ end | |
| 48 52 | The TaskValidator enforces a specific format for task lists with a strong focus on error handling: |
| 49 53 | |
| 50 54 | - Task IDs must follow a consistent pattern: 2-4 uppercase letters followed by 3-4 digits (e.g., SSH0001, SCP0001, ERR001, REF0002) |
| 51 | - - Subtasks must use the same prefix as their parent task (e.g., SSH0001-1 for a subtask of SSH0001) |
| 55 | + - Subtasks can use either numeric suffixes (SSH0001-1) or letter suffixes for checkbox style (SSH0001a) |
| 56 | + - Checkbox subtasks are supported: `- [ ] Subtask description [SSH0001a]` |
| 57 | + - Dependencies field tracks relationships between tasks |
| 58 | + - Code Quality KPIs enforce limits: max 5 functions/module, 15 lines/function, call depth 2 |
| 59 | + - Task categories with specific number ranges: |
| 60 | + * Core infrastructure: 1-99 |
| 61 | + * Features: 100-199 |
| 62 | + * Documentation: 200-299 |
| 63 | + * Testing: 300-399 |
| 52 64 | - Main tasks and subtasks have different error handling section requirements: |
| 53 65 | * Main tasks: Comprehensive error handling documentation including GenServer specifics |
| 54 66 | * Subtasks: Simplified error handling focused on task-specific approaches |
| 55 | - - Other required sections include Description, Status, Priority, etc. |
| 67 | + - Other required sections include Description, Status, Priority, Dependencies, etc. |
| 56 68 | - Tasks marked as "In Progress" must have subtasks |
| 57 69 | - Review ratings must follow the specified format (1-5 scale) |
| 70 | + - Support for reference definitions to reduce repetition (e.g., {{error-handling-main}}) |
| 58 71 | |
| 59 72 | ### Error Handling Requirements |
| 60 73 | |
| 61 | - Main tasks must include the following comprehensive error handling sections: |
| 74 | + Main tasks must include the following comprehensive error handling sections (or use the `{{error-handling-main}}` reference): |
| 62 75 | |
| 63 76 | ```markdown |
| 64 77 | **Error Handling** |
| @@ -80,6 +93,8 @@ Main tasks must include the following comprehensive error handling sections: | |
| 80 93 | - Process linking considerations |
| 81 94 | ``` |
| 82 95 | |
| 96 | + Or simply use: `{{error-handling-main}}` |
| 97 | + |
| 83 98 | Subtasks have a simplified error handling format: |
| 84 99 | |
| 85 100 | ```markdown |
| @@ -90,6 +105,105 @@ Subtasks have a simplified error handling format: | |
| 90 105 | - Monitoring approach |
| 91 106 | ``` |
| 92 107 | |
| 108 | + Or use: `{{error-handling-subtask}}` |
| 109 | + |
| 110 | + ### Dependencies |
| 111 | + |
| 112 | + Tasks can specify dependencies on other tasks using the Dependencies field: |
| 113 | + |
| 114 | + ```markdown |
| 115 | + **Dependencies** |
| 116 | + - SSH0001 (Authentication must be complete) |
| 117 | + - ERR001 (Error handling framework required) |
| 118 | + ``` |
| 119 | + |
| 120 | + The validator ensures all referenced tasks exist in the task list. |
| 121 | + |
| 122 | + ### Checkbox Subtasks |
| 123 | + |
| 124 | + Subtasks can use checkbox format for better visual tracking: |
| 125 | + |
| 126 | + ```markdown |
| 127 | + ## Task Details |
| 128 | + |
| 129 | + ### SSH0001: SSH Session Initialization |
| 130 | + |
| 131 | + **Subtasks** |
| 132 | + - [x] Connection setup [SSH0001a] |
| 133 | + - [ ] Authentication flow [SSH0001b] |
| 134 | + - [ ] Session establishment [SSH0001c] |
| 135 | + ``` |
| 136 | + |
| 137 | + ### Code Quality KPIs |
| 138 | + |
| 139 | + All tasks must include code quality metrics that adhere to these limits: |
| 140 | + - Maximum functions per module: 5 |
| 141 | + - Maximum lines per function: 15 |
| 142 | + - Maximum call depth: 2 |
| 143 | + |
| 144 | + ```markdown |
| 145 | + **Code Quality KPIs** |
| 146 | + - Functions per module: 3 |
| 147 | + - Lines per function: 12 |
| 148 | + - Call depth: 2 |
| 149 | + ``` |
| 150 | + |
| 151 | + Or use: `{{standard-kpis}}` |
| 152 | + |
| 153 | + ### Reference Definitions |
| 154 | + |
| 155 | + To reduce repetition in task lists, you can define reusable content blocks: |
| 156 | + |
| 157 | + ```markdown |
| 158 | + ## Reference Definitions |
| 159 | + |
| 160 | + ### error-handling-main |
| 161 | + **Error Handling** |
| 162 | + **Core Principles** |
| 163 | + - Pass raw errors |
| 164 | + ... |
| 165 | + |
| 166 | + ### standard-kpis |
| 167 | + **Code Quality KPIs** |
| 168 | + - Functions per module: 3 |
| 169 | + ... |
| 170 | + ``` |
| 171 | + |
| 172 | + Then use them in tasks with `{{reference-name}}`: |
| 173 | + |
| 174 | + ```markdown |
| 175 | + ### SSH0001: Some task |
| 176 | + |
| 177 | + **Dependencies** |
| 178 | + - None |
| 179 | + |
| 180 | + {{standard-kpis}} |
| 181 | + |
| 182 | + {{error-handling-main}} |
| 183 | + |
| 184 | + **Status**: Planned |
| 185 | + ``` |
| 186 | + |
| 187 | + This significantly reduces file size and maintains consistency across tasks. |
| 188 | + |
| 189 | + ### Task Categories |
| 190 | + |
| 191 | + Tasks are organized into categories based on their ID number: |
| 192 | + - **Core Infrastructure (1-99)**: Essential system components |
| 193 | + - **Features (100-199)**: User-facing functionality |
| 194 | + - **Documentation (200-299)**: Documentation tasks |
| 195 | + - **Testing (300-399)**: Test implementation |
| 196 | + |
| 197 | + Each category has specific required sections. For example, feature tasks require: |
| 198 | + - Feature Specification |
| 199 | + - User Impact |
| 200 | + - Integration Points |
| 201 | + |
| 202 | + Documentation tasks require: |
| 203 | + - Documentation Scope |
| 204 | + - Target Audience |
| 205 | + - Related Documents |
| 206 | + |
| 93 207 | ## Multi-Project Support |
| 94 208 | |
| 95 209 | The task validator supports multiple project prefixes in the same task list. Each prefix typically represents a different component or subproject: |
| @@ -2,9 +2,9 @@ | |
| 2 2 | [{<<"Docs">>,<<"https://hexdocs.pm/task_validator">>}, |
| 3 3 | {<<"GitHub">>,<<"https://github.com/ZenHive/task_validator">>}]}. |
| 4 4 | {<<"name">>,<<"task_validator">>}. |
| 5 | - {<<"version">>,<<"0.5.0">>}. |
| 5 | + {<<"version">>,<<"0.6.0">>}. |
| 6 6 | {<<"description">>, |
| 7 | - <<"A library for validating Markdown task lists with structured format specifications.\nEnforces error handling documentation, supports multi-project prefixes (SSH0001, ERR001),\nand ensures consistent task tracking with ID formats and required sections.">>}. |
| 7 | + <<"A library for validating Markdown task lists with structured format specifications.\nFeatures: checkbox subtasks, dependencies, code quality KPIs, task categories,\nmulti-project prefixes, and comprehensive error handling documentation.">>}. |
| 8 8 | {<<"elixir">>,<<"~> 1.18">>}. |
| 9 9 | {<<"app">>,<<"task_validator">>}. |
| 10 10 | {<<"files">>, |
| @@ -13,24 +13,63 @@ defmodule Mix.Tasks.TaskValidator.CreateTemplate do | |
| 13 13 | ## Options |
| 14 14 | |
| 15 15 | --path Path where to create the TaskList.md file (default: ./TaskList.md) |
| 16 | - --prefix Project prefix for example tasks (default: PRJ) |
| 16 | + --prefix Project prefix for example tasks (default: PRJ) |
| 17 | + --category Task category to generate template for: core, features, documentation, testing (default: features) |
| 17 18 | |
| 18 19 | ## Example |
| 19 20 | |
| 20 21 | mix task_validator.create_template |
| 21 | - mix task_validator.create_template --path ./docs/TaskList.md --prefix SSH |
| 22 | + mix task_validator.create_template --path ./docs/TaskList.md --prefix SSH --category core |
| 23 | + mix task_validator.create_template --category documentation |
| 22 24 | """ |
| 23 25 | |
| 24 26 | use Mix.Task |
| 25 27 | |
| 26 | - @template """ |
| 28 | + @reference_definitions """ |
| 29 | + |
| 30 | + ## Reference Definitions |
| 31 | + |
| 32 | + ### error-handling-main |
| 33 | + **Error Handling** |
| 34 | + **Core Principles** |
| 35 | + - Pass raw errors |
| 36 | + - Use {:ok, result} | {:error, reason} |
| 37 | + - Let it crash |
| 38 | + **Error Implementation** |
| 39 | + - No wrapping |
| 40 | + - Minimal rescue |
| 41 | + - function/1 & /! versions |
| 42 | + **Error Examples** |
| 43 | + - Raw error passthrough |
| 44 | + - Simple rescue case |
| 45 | + - Supervisor handling |
| 46 | + **GenServer Specifics** |
| 47 | + - Handle_call/3 error pattern |
| 48 | + - Terminate/2 proper usage |
| 49 | + - Process linking considerations |
| 50 | + |
| 51 | + ### error-handling-subtask |
| 52 | + **Error Handling** |
| 53 | + **Task-Specific Approach** |
| 54 | + - Error pattern for this task |
| 55 | + **Error Reporting** |
| 56 | + - Monitoring approach |
| 57 | + |
| 58 | + ### standard-kpis |
| 59 | + **Code Quality KPIs** |
| 60 | + - Functions per module: 3 |
| 61 | + - Lines per function: 12 |
| 62 | + - Call depth: 2 |
| 63 | + """ |
| 64 | + |
| 65 | + @features_template """ |
| 27 66 | # Project Task List |
| 28 67 | |
| 29 68 | ## Current Tasks |
| 30 69 | |
| 31 70 | | ID | Description | Status | Priority | Assignee | Review Rating | |
| 32 71 | | --- | --- | --- | --- | --- | --- | |
| 33 | - | <%= @prefix %>0001 | Implement core functionality | In Progress | High | - | - | |
| 72 | + | <%= @prefix %><%= @task_number %> | Implement core functionality | In Progress | High | - | - | |
| 34 73 | | <%= @prefix %>0002 | Add documentation framework | Planned | Medium | - | - | |
| 35 74 | |
| 36 75 | ## Completed Tasks |
| @@ -41,7 +80,7 @@ defmodule Mix.Tasks.TaskValidator.CreateTemplate do | |
| 41 80 | |
| 42 81 | ## Active Task Details |
| 43 82 | |
| 44 | - ### <%= @prefix %>0001: Implement core functionality |
| 83 | + ### <%= @prefix %><%= @task_number %>: Implement core functionality |
| 45 84 | |
| 46 85 | **Description** |
| 47 86 | Develop and implement the core functionality with a focus on maintainability and extensibility. |
| @@ -89,53 +128,46 @@ defmodule Mix.Tasks.TaskValidator.CreateTemplate do | |
| 89 128 | **TypeSpec Verification** |
| 90 129 | Use Dialyzer to verify type correctness |
| 91 130 | |
| 92 | - **Error Handling** |
| 93 | - **Core Principles** |
| 94 | - - Pass raw errors |
| 95 | - - Use {:ok, result} | {:error, reason} |
| 96 | - - Let it crash |
| 97 | - **Error Implementation** |
| 98 | - - No wrapping |
| 99 | - - Minimal rescue |
| 100 | - - function/1 & /! versions |
| 101 | - **Error Examples** |
| 102 | - - Raw error passthrough |
| 103 | - - Simple rescue case |
| 104 | - - Supervisor handling |
| 105 | - **GenServer Specifics** |
| 106 | - - Handle_call/3 error pattern |
| 107 | - - Terminate/2 proper usage |
| 108 | - - Process linking considerations |
| 131 | + **Dependencies** |
| 132 | + - None |
| 109 133 | |
| 110 | - **Status**: In Progress |
| 134 | + {{standard-kpis}} |
| 135 | + |
| 136 | + {{error-handling-main}} |
| 137 | + |
| 138 | + **Status**: Planned |
| 111 139 | **Priority**: High |
| 112 140 | |
| 113 | - #### 1. Basic structure implementation (<%= @prefix %>0001-1) |
| 141 | + **Architecture Notes** |
| 142 | + Core system component design |
| 143 | + |
| 144 | + **Complexity Assessment** |
| 145 | + Medium - Requires careful design but implementation is straightforward |
| 146 | + |
| 147 | + **System Impact** |
| 148 | + Foundation for other modules |
| 149 | + |
| 150 | + **Dependency Analysis** |
| 151 | + No external dependencies |
| 152 | + |
| 153 | + #### 1. Basic structure implementation (<%= @prefix %><%= @task_number %>-1) |
| 114 154 | |
| 115 155 | **Description** |
| 116 156 | Create the foundational structure and interfaces |
| 117 157 | |
| 118 | - **Error Handling** |
| 119 | - **Task-Specific Approach** |
| 120 | - - Error pattern for this task |
| 121 | - **Error Reporting** |
| 122 | - - Monitoring approach |
| 158 | + {{error-handling-subtask}} |
| 123 159 | |
| 124 160 | **Status**: Completed |
| 125 161 | **Review Rating**: 4.5 |
| 126 162 | |
| 127 | - #### 2. Essential features (<%= @prefix %>0001-2) |
| 163 | + #### 2. Essential features (<%= @prefix %><%= @task_number %>-2) |
| 128 164 | |
| 129 165 | **Description** |
| 130 166 | Implement core features and functionality |
| 131 167 | |
| 132 | - **Error Handling** |
| 133 | - **Task-Specific Approach** |
| 134 | - - Error pattern for this task |
| 135 | - **Error Reporting** |
| 136 | - - Monitoring approach |
| 168 | + {{error-handling-subtask}} |
| 137 169 | |
| 138 | - **Status**: In Progress |
| 170 | + **Status**: Planned |
| 139 171 | |
| 140 172 | ### <%= @prefix %>0002: Add documentation framework |
| 141 173 | |
| @@ -180,42 +212,463 @@ defmodule Mix.Tasks.TaskValidator.CreateTemplate do | |
| 180 212 | **TypeSpec Verification** |
| 181 213 | Regular verification of documentation accuracy |
| 182 214 | |
| 183 | - **Error Handling** |
| 184 | - **Core Principles** |
| 185 | - - Pass raw errors |
| 186 | - - Use {:ok, result} | {:error, reason} |
| 187 | - - Let it crash |
| 188 | - **Error Implementation** |
| 189 | - - No wrapping |
| 190 | - - Minimal rescue |
| 191 | - - function/1 & /! versions |
| 192 | - **Error Examples** |
| 193 | - - Raw error passthrough |
| 194 | - - Simple rescue case |
| 195 | - - Supervisor handling |
| 196 | - **GenServer Specifics** |
| 197 | - - Handle_call/3 error pattern |
| 198 | - - Terminate/2 proper usage |
| 199 | - - Process linking considerations |
| 215 | + **Dependencies** |
| 216 | + - None |
| 217 | + |
| 218 | + {{standard-kpis}} |
| 219 | + |
| 220 | + {{error-handling-main}} |
| 200 221 | |
| 201 222 | **Status**: Planned |
| 202 223 | **Priority**: Medium |
| 224 | + |
| 225 | + **Architecture Notes** |
| 226 | + Documentation framework choice |
| 227 | + |
| 228 | + **Complexity Assessment** |
| 229 | + Low - Standard documentation setup |
| 230 | + |
| 231 | + **System Impact** |
| 232 | + Developer experience improvement |
| 233 | + |
| 234 | + **Dependency Analysis** |
| 235 | + Depends on core module |
| 236 | + """ |
| 237 | + |
| 238 | + @core_template """ |
| 239 | + # Project Task List |
| 240 | + |
| 241 | + ## Current Tasks |
| 242 | + |
| 243 | + | ID | Description | Status | Priority | Assignee | Review Rating | |
| 244 | + | --- | --- | --- | --- | --- | --- | |
| 245 | + | <%= @prefix %>0001 | Core architecture implementation | Planned | High | - | - | |
| 246 | + | <%= @prefix %>0002 | Add documentation framework | Planned | Medium | - | - | |
| 247 | + |
| 248 | + ## Completed Tasks |
| 249 | + |
| 250 | + | ID | Description | Status | Completed By | Review Rating | |
| 251 | + | --- | --- | --- | ------------ | ------------- | |
| 252 | + | <%= @prefix %>0003 | Project setup | Completed | @developer | 4.5 | |
| 253 | + |
| 254 | + ## Active Task Details |
| 255 | + |
| 256 | + ### <%= @prefix %><%= @task_number %>: Core architecture implementation |
| 257 | + |
| 258 | + **Description** |
| 259 | + Implement core system architecture with focus on performance and reliability. |
| 260 | + |
| 261 | + **Simplicity Progression Plan** |
| 262 | + 1. Design core interfaces |
| 263 | + 2. Implement basic functionality |
| 264 | + 3. Add error handling |
| 265 | + 4. Optimize performance |
| 266 | + |
| 267 | + **Simplicity Principle** |
| 268 | + Focus on essential functionality with minimal complexity and maximum reliability. |
| 269 | + |
| 270 | + **Abstraction Evaluation** |
| 271 | + Low-level implementation with clear separation of concerns |
| 272 | + |
| 273 | + **Requirements** |
| 274 | + - High performance architecture |
| 275 | + - Robust error handling |
| 276 | + - Scalable design patterns |
| 277 | + - Comprehensive testing |
| 278 | + |
| 279 | + **ExUnit Test Requirements** |
| 280 | + - Performance benchmarks |
| 281 | + - Load testing |
| 282 | + - Failure scenario testing |
| 283 | + |
| 284 | + **Integration Test Scenarios** |
| 285 | + - System integration tests |
| 286 | + - Performance under load |
| 287 | + - Failure recovery testing |
| 288 | + |
| 289 | + **Typespec Requirements** |
| 290 | + - Core type definitions |
| 291 | + - Interface specifications |
| 292 | + |
| 293 | + **TypeSpec Documentation** |
| 294 | + Complete documentation of all core types and interfaces |
| 295 | + |
| 296 | + **TypeSpec Verification** |
| 297 | + Strict type checking with Dialyzer |
| 298 | + |
| 299 | + **Dependencies** |
| 300 | + - None |
| 301 | + |
| 302 | + **Code Quality KPIs** |
| 303 | + - Functions per module: 3 |
| 304 | + - Lines per function: 12 |
| 305 | + - Call depth: 2 |
| 306 | + |
| 307 | + **Architecture Notes** |
| 308 | + Core system design using proven patterns and minimal dependencies |
| 309 | + |
| 310 | + **Complexity Assessment** |
| 311 | + Medium complexity focused on correctness and performance |
| 312 | + |
| 313 | + {{error-handling-main}} |
| 314 | + |
| 315 | + **Status**: Planned |
| 316 | + **Priority**: High |
| 317 | + |
| 318 | + **Architecture Notes** |
| 319 | + Simple initial design |
| 320 | + |
| 321 | + **Complexity Assessment** |
| 322 | + Low - Basic structure only |
| 323 | + |
| 324 | + #### 1. Basic structure implementation (<%= @prefix %><%= @task_number %>-1) |
| 325 | + |
| 326 | + **Description** |
| 327 | + Create the foundational architecture and core interfaces |
| 328 | + |
| 329 | + {{error-handling-subtask}} |
| 330 | + |
| 331 | + **Status**: Completed |
| 332 | + **Review Rating**: 4.5 |
| 333 | + |
| 334 | + #### 2. Performance optimization (<%= @prefix %><%= @task_number %>-2) |
| 335 | + |
| 336 | + **Description** |
| 337 | + Implement performance optimizations and monitoring |
| 338 | + |
| 339 | + {{error-handling-subtask}} |
| 340 | + |
| 341 | + **Status**: Planned |
| 342 | + |
| 343 | + |
| 344 | + ### <%= @prefix %>0002: Add documentation framework |
| 345 | + |
| 346 | + **Description** |
| 347 | + Set up and implement the documentation framework for the project. |
| 348 | + |
| 349 | + **Simplicity Progression Plan** |
| 350 | + 1. Set up documentation tools |
| 351 | + 2. Create basic structure |
| 352 | + 3. Add API documentation |
| 353 | + 4. Create user guides |
| 354 | + |
| 355 | + **Simplicity Principle** |
| 356 | + Keep documentation clear, concise, and maintainable |
| 357 | + |
| 358 | + **Abstraction Evaluation** |
| 359 | + Low - straightforward documentation structure |
| 360 | + |
| 361 | + **Requirements** |
| 362 | + - Documentation tool setup |
| 363 | + - API documentation |
| 364 | + - User guides |
| 365 | + - Example code |
| 366 | + |
| 367 | + **ExUnit Test Requirements** |
| 368 | + - Documentation generation tests |
| 369 | + - Link validation |
| 370 | + - Code example tests |
| 371 | + |
| 372 | + **Integration Test Scenarios** |
| 373 | + - Documentation generation |
| 374 | + - Format validation |
| 375 | + - Cross-reference checks |
| 376 | + |
| 377 | + **Typespec Requirements** |
| 378 | + - Document type specifications |
| 379 | + - Include type examples |
| 380 | + |
| 381 | + **TypeSpec Documentation** |
| 382 | + Clear documentation of all public types |
| 383 | + |
| 384 | + **TypeSpec Verification** |
| 385 | + Regular verification of documentation accuracy |
| 386 | + |
| 387 | + **Dependencies** |
| 388 | + - <%= @prefix %>0001 |
| 389 | + |
| 390 | + {{standard-kpis}} |
| 391 | + |
| 392 | + **Architecture Notes** |
| 393 | + Standard documentation framework |
| 394 | + |
| 395 | + **Complexity Assessment** |
| 396 | + Low - Standard tooling |
| 397 | + |
| 398 | + {{error-handling-main}} |
| 399 | + |
| 400 | + **Status**: Planned |
| 401 | + **Priority**: Medium |
| 402 | + |
| 403 | + ## Completed Task Details |
| 404 | + |
| 405 | + ### <%= @prefix %>0003: Project setup |
| 406 | + |
| 407 | + **Description** |
| 408 | + Initial project setup and structure implementation |
| 409 | + |
| 410 | + **Simplicity Progression Plan** |
| 411 | + 1. Create directory structure |
| 412 | + 2. Set up build configuration |
| 413 | + 3. Initialize version control |
| 414 | + |
| 415 | + **Simplicity Principle** |
| 416 | + Standard project layout with minimal configuration |
| 417 | + |
| 418 | + **Abstraction Evaluation** |
| 419 | + Low - Direct implementation |
| 420 | + |
| 421 | + **Requirements** |
| 422 | + - Directory structure |
| 423 | + - Build configuration |
| 424 | + - Initial documentation |
| 425 | + |
| 426 | + **ExUnit Test Requirements** |
| 427 | + - Verify build process |
| 428 | + - Test configuration loading |
| 429 | + |
| 430 | + **Integration Test Scenarios** |
| 431 | + - Full project build and test |
| 432 | + |
| 433 | + **Typespec Requirements** |
| 434 | + - Basic type definitions |
| 435 | + - Module specifications |
| 436 | + |
| 437 | + **TypeSpec Documentation** |
| 438 | + Document core type specifications |
| 439 | + |
| 440 | + **TypeSpec Verification** |
| 441 | + Initial Dialyzer setup and verification |
| 442 | + |
| 443 | + **Dependencies** |
| 444 | + - None |
| 445 | + |
| 446 | + {{standard-kpis}} |
| 447 | + |
| 448 | + **Architecture Notes** |
| 449 | + Simple standard structure |
| 450 | + |
| 451 | + **Complexity Assessment** |
| 452 | + Low - Basic setup only |
| 453 | + |
| 454 | + {{error-handling-main}} |
| 455 | + |
| 456 | + **Error Handling Implementation** |
| 457 | + Standard error patterns |
| 458 | + |
| 459 | + **Status**: Completed |
| 460 | + **Priority**: High |
| 461 | + |
| 462 | + **Implementation Notes** |
| 463 | + Basic project structure created |
| 464 | + |
| 465 | + **Maintenance Impact** |
| 466 | + Minimal - standard structure |
| 467 | + |
| 468 | + **Review Rating**: 4.5 |
| 469 | + |
| 470 | + """ |
| 471 | + |
| 472 | + @documentation_template """ |
| 473 | + # Project Task List |
| 474 | + |
| 475 | + ## Current Tasks |
| 476 | + |
| 477 | + | ID | Description | Status | Priority | Assignee | Review Rating | |
| 478 | + | --- | --- | --- | --- | --- | --- | |
| 479 | + | <%= @prefix %><%= @task_number %> | Create comprehensive documentation | Planned | Medium | - | - | |
| 480 | + |
| 481 | + ## Completed Tasks |
| 482 | + |
| 483 | + | ID | Description | Status | Completed By | Review Rating | |
| 484 | + | --- | --- | --- | ------------ | ------------- | |
| 485 | + |
| 486 | + ## Active Task Details |
| 487 | + |
| 488 | + ### <%= @prefix %><%= @task_number %>: Create comprehensive documentation |
| 489 | + |
| 490 | + **Description** |
| 491 | + Develop user-focused documentation with clear examples and usage patterns. |
| 492 | + |
| 493 | + **Simplicity Progression Plan** |
| 494 | + 1. Analyze target audience |
| 495 | + 2. Create content structure |
| 496 | + 3. Write core documentation |
| 497 | + 4. Add examples and tutorials |
| 498 | + |
| 499 | + **Simplicity Principle** |
| 500 | + Clear, concise documentation that enables users to succeed quickly. |
| 501 | + |
| 502 | + **Abstraction Evaluation** |
| 503 | + Documentation structure that hides complexity while providing necessary details |
| 504 | + |
| 505 | + **Requirements** |
| 506 | + - User-focused content |
| 507 | + - Clear examples |
| 508 | + - Comprehensive API documentation |
| 509 | + - Getting started guides |
| 510 | + |
| 511 | + **ExUnit Test Requirements** |
| 512 | + - Documentation example tests |
| 513 | + - Link validation |
| 514 | + - Code snippet verification |
| 515 | + |
| 516 | + **Integration Test Scenarios** |
| 517 | + - Documentation build process |
| 518 | + - Cross-reference validation |
| 519 | + - Example code execution |
| 520 | + |
| 521 | + **Typespec Requirements** |
| 522 | + - Documented type examples |
| 523 | + - Usage pattern documentation |
| 524 | + |
| 525 | + **TypeSpec Documentation** |
| 526 | + Clear examples of type usage in documentation |
| 527 | + |
| 528 | + **TypeSpec Verification** |
| 529 | + Ensure all documented types are valid |
| 530 | + |
| 531 | + **Dependencies** |
| 532 | + - None |
| 533 | + |
| 534 | + **Code Quality KPIs** |
| 535 | + - Functions per module: 3 |
| 536 | + - Lines per function: 12 |
| 537 | + - Call depth: 2 |
| 538 | + |
| 539 | + **Content Strategy** |
| 540 | + User-focused approach with progressive disclosure and practical examples |
| 541 | + |
| 542 | + **Audience Analysis** |
| 543 | + Target developers with varying experience levels, prioritize clarity over completeness |
| 544 | + |
| 545 | + {{error-handling-main}} |
| 546 | + |
| 547 | + **Status**: Planned |
| 548 | + **Priority**: Medium |
| 549 | + **Dependencies**: None |
| 550 | + """ |
| 551 | + |
| 552 | + @testing_template """ |
| 553 | + # Project Task List |
| 554 | + |
| 555 | + ## Current Tasks |
| 556 | + |
| 557 | + | ID | Description | Status | Priority | Assignee | Review Rating | |
| 558 | + | --- | --- | --- | --- | --- | --- | |
| 559 | + | <%= @prefix %><%= @task_number %> | Implement comprehensive testing strategy | Planned | High | - | - | |
| 560 | + |
| 561 | + ## Completed Tasks |
| 562 | + |
| 563 | + | ID | Description | Status | Completed By | Review Rating | |
| 564 | + | --- | --- | --- | ------------ | ------------- | |
| 565 | + |
| 566 | + ## Active Task Details |
| 567 | + |
| 568 | + ### <%= @prefix %><%= @task_number %>: Implement comprehensive testing strategy |
| 569 | + |
| 570 | + **Description** |
| 571 | + Design and implement comprehensive testing approach covering unit, integration, and performance testing. |
| 572 | + |
| 573 | + **Simplicity Progression Plan** |
| 574 | + 1. Define testing strategy |
| 575 | + 2. Implement unit tests |
| 576 | + 3. Add integration tests |
| 577 | + 4. Set up performance testing |
| 578 | + |
| 579 | + **Simplicity Principle** |
| 580 | + Comprehensive test coverage with maintainable test code and clear assertions. |
| 581 | + |
| 582 | + **Abstraction Evaluation** |
| 583 | + Test abstractions that simplify test writing while maintaining clarity |
| 584 | + |
| 585 | + **Requirements** |
| 586 | + - Unit test coverage |
| 587 | + - Integration test scenarios |
| 588 | + - Performance benchmarks |
| 589 | + - Continuous integration |
| 590 | + |
| 591 | + **ExUnit Test Requirements** |
| 592 | + - Comprehensive unit tests |
| 593 | + - Property-based testing |
| 594 | + - Mock and stub strategies |
| 595 | + |
| 596 | + **Integration Test Scenarios** |
| 597 | + - End-to-end workflow testing |
| 598 | + - External service integration |
| 599 | + - Error condition testing |
| 600 | + |
| 601 | + **Typespec Requirements** |
| 602 | + - Test helper type definitions |
| 603 | + - Mock interface specifications |
| 604 | + |
| 605 | + **TypeSpec Documentation** |
| 606 | + Clear documentation of test interfaces and utilities |
| 607 | + |
| 608 | + **TypeSpec Verification** |
| 609 | + Type-safe test utilities and helpers |
| 610 | + |
| 611 | + **Dependencies** |
| 612 | + - None |
| 613 | + |
| 614 | + **Code Quality KPIs** |
| 615 | + - Functions per module: 3 |
| 616 | + - Lines per function: 12 |
| 617 | + - Call depth: 2 |
| 618 | + |
| 619 | + **Test Strategy** |
| 620 | + Comprehensive approach using unit tests, integration tests, and performance benchmarks |
| 621 | + |
| 622 | + **Coverage Requirements** |
| 623 | + Minimum 90% code coverage with focus on critical business logic and error paths |
| 624 | + |
| 625 | + {{error-handling-main}} |
| 626 | + |
| 627 | + **Status**: Planned |
| 628 | + **Priority**: High |
| 629 | + |
| 630 | + **Architecture Notes** |
| 631 | + Simple initial design |
| 632 | + |
| 633 | + **Complexity Assessment** |
| 634 | + Low - Basic structure only |
| 203 635 | """ |
| 204 636 | |
| 205 637 | def run(args) do |
| 206 | - {options, _, _} = OptionParser.parse(args, strict: [path: :string, prefix: :string]) |
| 638 | + {options, _, _} = |
| 639 | + OptionParser.parse(args, strict: [path: :string, prefix: :string, category: :string]) |
| 640 | + |
| 207 641 | path = options[:path] || "TaskList.md" |
| 208 642 | prefix = options[:prefix] || "PRJ" |
| 643 | + category = options[:category] || "features" |
| 644 | + |
| 645 | + # Validate category |
| 646 | + valid_categories = ["core", "features", "documentation", "testing"] |
| 647 | + |
| 648 | + unless category in valid_categories do |
| 649 | + Mix.shell().error( |
| 650 | + "Invalid category: #{category}. Valid options: #{Enum.join(valid_categories, ", ")}" |
| 651 | + ) |
| 652 | + |
| 653 | + exit({:shutdown, 1}) |
| 654 | + end |
| 209 655 | |
| 210 656 | if File.exists?(path) do |
| 211 657 | Mix.shell().yes?("File #{path} already exists. Overwrite?") || exit(:normal) |
| 212 658 | end |
| 213 659 | |
| 214 | - content = EEx.eval_string(@template, assigns: [prefix: prefix]) |
| 660 | + # Generate appropriate task number for the category |
| 661 | + task_number = get_category_task_number(category) |
| 662 | + template = get_template_for_category(category) |
| 663 | + |
| 664 | + content = |
| 665 | + EEx.eval_string(template <> @reference_definitions, |
| 666 | + assigns: [prefix: prefix, task_number: task_number] |
| 667 | + ) |
| 215 668 | |
| 216 669 | case File.write(path, content) do |
| 217 670 | :ok -> |
| 218 | - Mix.shell().info("✅ Created template task list at #{path}") |
| 671 | + Mix.shell().info("✅ Created #{category} category template task list at #{path}") |
| 219 672 | validate_template(path) |
| 220 673 | |
| 221 674 | {:error, reason} -> |
| @@ -224,6 +677,24 @@ defmodule Mix.Tasks.TaskValidator.CreateTemplate do | |
| 224 677 | end |
| 225 678 | end |
| 226 679 | |
| 680 | + defp get_category_task_number(category) do |
| 681 | + case category do |
| 682 | + "core" -> "0001" |
| 683 | + "features" -> "0101" |
| 684 | + "documentation" -> "0201" |
| 685 | + "testing" -> "0301" |
| 686 | + end |
| 687 | + end |
| 688 | + |
| 689 | + defp get_template_for_category(category) do |
| 690 | + case category do |
| 691 | + "core" -> @core_template |
| 692 | + "features" -> @features_template |
| 693 | + "documentation" -> @documentation_template |
| 694 | + "testing" -> @testing_template |
| 695 | + end |
| 696 | + end |
| 697 | + |
| 227 698 | defp validate_template(path) do |
| 228 699 | case TaskValidator.validate_file(path) do |
| 229 700 | {:ok, _} -> |
| @@ -68,9 +68,23 @@ defmodule TaskValidator do | |
| 68 68 | @valid_statuses ["Planned", "In Progress", "Review", "Completed", "Blocked"] |
| 69 69 | @valid_priorities ["Critical", "High", "Medium", "Low"] |
| 70 70 | # Support various prefixes (2-4 uppercase letters) followed by digits |
| 71 | - @id_regex ~r/^[A-Z]{2,4}\d{3,4}(-\d+)?$/ |
| 71 | + # Also support letter suffix for checkbox-style subtasks (e.g., WNX0019a) |
| 72 | + @id_regex ~r/^[A-Z]{2,4}\d{3,4}(-\d+|[a-z])?$/ |
| 72 73 | @rating_regex ~r/^([1-5](\.\d)?)\s*(\(partial\))?$/ |
| 73 74 | |
| 75 | + # Code quality KPI limits |
| 76 | + @max_functions_per_module 5 |
| 77 | + @max_lines_per_function 15 |
| 78 | + @max_call_depth 2 |
| 79 | + |
| 80 | + # Task category ranges |
| 81 | + @category_ranges %{ |
| 82 | + "core" => {1, 99}, |
| 83 | + "features" => {100, 199}, |
| 84 | + "documentation" => {200, 299}, |
| 85 | + "testing" => {300, 399} |
| 86 | + } |
| 87 | + |
| 74 88 | # Required sections for error handling - machine-readable, token-optimized format |
| 75 89 | @error_handling_sections [ |
| 76 90 | "**Error Handling**", |
| @@ -118,13 +132,98 @@ defmodule TaskValidator do | |
| 118 132 | def validate_file(file_path) do |
| 119 133 | with {:ok, content} <- File.read(file_path), |
| 120 134 | {:ok, lines} <- {:ok, String.split(content, "\n")}, |
| 121 | - {:ok, tasks} <- extract_tasks(lines), |
| 135 | + {:ok, references} <- extract_references(lines), |
| 136 | + {:ok, resolved_lines} <- resolve_references(lines, references), |
| 137 | + {:ok, tasks} <- extract_tasks(resolved_lines), |
| 122 138 | :ok <- validate_task_ids(tasks), |
| 123 | - :ok <- validate_task_details(lines, tasks) do |
| 139 | + :ok <- validate_task_details(resolved_lines, tasks) do |
| 124 140 | {:ok, "TaskList.md validation passed!"} |
| 125 141 | end |
| 126 142 | end |
| 127 143 | |
| 144 | + @doc """ |
| 145 | + Extracts reference definitions from the content. |
| 146 | + Reference definitions are in the format: |
| 147 | + ## Reference Definitions |
| 148 | + ### error-handling |
| 149 | + **Error Handling** |
| 150 | + **Core Principles** |
| 151 | + ... |
| 152 | + """ |
| 153 | + @spec extract_references(list(String.t())) :: {:ok, map()} |
| 154 | + def extract_references(lines) do |
| 155 | + # Find the Reference Definitions section |
| 156 | + ref_start = Enum.find_index(lines, &(&1 == "## Reference Definitions")) |
| 157 | + |
| 158 | + if ref_start do |
| 159 | + references = |
| 160 | + lines |
| 161 | + |> Enum.drop(ref_start + 1) |
| 162 | + |> extract_reference_blocks(%{}) |
| 163 | + |
| 164 | + {:ok, references} |
| 165 | + else |
| 166 | + # No reference definitions found, that's OK |
| 167 | + {:ok, %{}} |
| 168 | + end |
| 169 | + end |
| 170 | + |
| 171 | + defp extract_reference_blocks([], acc), do: acc |
| 172 | + |
| 173 | + defp extract_reference_blocks(lines, acc) do |
| 174 | + case lines do |
| 175 | + ["### " <> ref_name | rest] -> |
| 176 | + # Extract content until next ### or end |
| 177 | + {content, remaining} = |
| 178 | + Enum.split_while(rest, fn line -> |
| 179 | + !String.starts_with?(line, "### ") && !String.starts_with?(line, "## ") |
| 180 | + end) |
| 181 | + |
| 182 | + # Store the reference content |
| 183 | + updated_acc = Map.put(acc, ref_name, content) |
| 184 | + extract_reference_blocks(remaining, updated_acc) |
| 185 | + |
| 186 | + [_ | rest] -> |
| 187 | + extract_reference_blocks(rest, acc) |
| 188 | + |
| 189 | + [] -> |
| 190 | + acc |
| 191 | + end |
| 192 | + end |
| 193 | + |
| 194 | + @doc """ |
| 195 | + Resolves references in the content by replacing {{ref-name}} with actual content. |
| 196 | + """ |
| 197 | + @spec resolve_references(list(String.t()), map()) :: {:ok, list(String.t())} |
| 198 | + def resolve_references(lines, references) do |
| 199 | + resolved_lines = |
| 200 | + Enum.map(lines, fn line -> |
| 201 | + # Check if line contains a reference like {{error-handling}} |
| 202 | + if String.contains?(line, "{{") && String.contains?(line, "}}") do |
| 203 | + case Regex.run(~r/\{\{([^}]+)\}\}/, line) do |
| 204 | + [_full_match, ref_name] -> |
| 205 | + case Map.get(references, ref_name) do |
| 206 | + nil -> |
| 207 | + # Reference not found, keep as is |
| 208 | + line |
| 209 | + |
| 210 | + ref_content -> |
| 211 | + # Replace the line with the reference content |
| 212 | + ref_content |
| 213 | + end |
| 214 | + |
| 215 | + _ -> |
| 216 | + line |
| 217 | + end |
| 218 | + else |
| 219 | + line |
| 220 | + end |
| 221 | + end) |
| 222 | + |> List.flatten() |
| 223 | + |
| 224 | + {:ok, resolved_lines} |
| 225 | + end |
| 226 | + |
| 128 227 | @doc """ |
| 129 228 | Extracts tasks from the TaskList.md content. |
| 130 229 | """ |
| @@ -309,32 +408,63 @@ defmodule TaskValidator do | |
| 309 408 | end |
| 310 409 | |
| 311 410 | defp extract_subtasks(task_content) do |
| 312 | - # Find lines starting with "#### " |
| 411 | + # Find lines starting with "#### " or checkbox format "- [ ]" |
| 313 412 | task_content |
| 314 413 | |> Enum.with_index() |
| 315 414 | |> Enum.filter(fn {line, _} -> |
| 316 | - String.match?(line, ~r/^#### \d+\./) |
| 415 | + String.match?(line, ~r/^#### \d+\./) || String.match?(line, ~r/^- \[[x\s]\]/) |
| 317 416 | end) |
| 318 417 | |> Enum.map(fn {line, idx} -> |
| 319 | - # Extract subtask ID from the line (PREFIX####-#) |
| 320 | - case Regex.run(~r/\(([A-Z]{2,4}\d{3,4}-\d+)\)/, line) do |
| 321 | - [_, subtask_id] -> %{id: subtask_id, line: idx} |
| 322 | - _ -> %{id: "INVALID_FORMAT", line: idx} |
| 418 | + cond do |
| 419 | + # Traditional numbered subtask format |
| 420 | + String.match?(line, ~r/^#### \d+\./) -> |
| 421 | + case Regex.run(~r/\(([A-Z]{2,4}\d{3,4}-\d+)\)/, line) do |
| 422 | + [_, subtask_id] -> %{id: subtask_id, line: idx, format: :numbered} |
| 423 | + _ -> %{id: "INVALID_FORMAT", line: idx, format: :numbered} |
| 424 | + end |
| 425 | + |
| 426 | + # Checkbox subtask format |
| 427 | + String.match?(line, ~r/^- \[[x\s]\]/) -> |
| 428 | + # Extract subtask ID from checkbox line |
| 429 | + # Support both formats: "- [ ] Description [CHK0001a]" and "- [ ] **CHK0001a**: Description" |
| 430 | + subtask_id = |
| 431 | + case Regex.run(~r/\[([A-Z]{2,4}\d{3,4}[a-z]?)\]$/, line) do |
| 432 | + [_, id] -> |
| 433 | + id |
| 434 | + |
| 435 | + _ -> |
| 436 | + case Regex.run(~r/\*\*([A-Z]{2,4}\d{3,4}[a-z]?)\*\*/, line) do |
| 437 | + [_, id] -> id |
| 438 | + _ -> "INVALID_FORMAT" |
| 439 | + end |
| 440 | + end |
| 441 | + |
| 442 | + # Determine if checkbox is checked |
| 443 | + checked = String.contains?(line, "[x]") |
| 444 | + %{id: subtask_id, line: idx, format: :checkbox, checked: checked} |
| 323 445 | end |
| 324 446 | end) |
| 325 447 | end |
| 326 448 | |
| 327 449 | defp validate_detailed_tasks(detailed_tasks) do |
| 450 | + # First, collect all valid task IDs for dependency validation |
| 451 | + all_task_ids = |
| 452 | + detailed_tasks |
| 453 | + |> Enum.flat_map(fn task -> |
| 454 | + [task.id | Enum.map(task.subtasks, & &1.id)] |
| 455 | + end) |
| 456 | + |> MapSet.new() |
| 457 | + |
| 328 458 | # Validate each detailed task |
| 329 459 | Enum.reduce_while(detailed_tasks, :ok, fn task, _acc -> |
| 330 | - case validate_task_structure(task) do |
| 460 | + case validate_task_structure(task, all_task_ids) do |
| 331 461 | :ok -> {:cont, :ok} |
| 332 462 | {:error, reason} -> {:halt, {:error, reason}} |
| 333 463 | end |
| 334 464 | end) |
| 335 465 | end |
| 336 466 | |
| 337 | - defp validate_task_structure(task) do |
| 467 | + defp validate_task_structure(task, all_task_ids) do |
| 338 468 | # First, check subtask prefix consistency |
| 339 469 | subtask_prefix_result = |
| 340 470 | if task.subtasks != [] do |
| @@ -382,8 +512,10 @@ defmodule TaskValidator do | |
| 382 512 | "**Typespec Requirements**", |
| 383 513 | "**TypeSpec Documentation**", |
| 384 514 | "**TypeSpec Verification**", |
| 515 | + "**Code Quality KPIs**", |
| 385 516 | "**Status**", |
| 386 | - "**Priority**" |
| 517 | + "**Priority**", |
| 518 | + "**Dependencies**" |
| 387 519 | ] |
| 388 520 | |
| 389 521 | # First check if all base required sections are present |
| @@ -435,44 +567,134 @@ defmodule TaskValidator do | |
| 435 567 | |
| 436 568 | if missing_sections == [] do |
| 437 569 | # Extract status and validate |
| 438 | - status_line = |
| 439 | - Enum.find(task.content, fn line -> String.starts_with?(line, "**Status**") end) |
| 570 | + status_idx = |
| 571 | + Enum.find_index(task.content, fn line -> String.starts_with?(line, "**Status**") end) |
| 440 572 | |
| 441 573 | status = |
| 442 | - if status_line do |
| 443 | - status_line |
| 444 | - |> String.replace("**Status**:", "") |
| 445 | - |> String.replace("**Status**", "") |
| 446 | - |> String.trim() |
| 447 | - else |
| 448 | - "MISSING" |
| 574 | + cond do |
| 575 | + is_nil(status_idx) -> |
| 576 | + "MISSING" |
| 577 | + |
| 578 | + # Check if status is on the same line (e.g., "**Status**: In Progress") |
| 579 | + String.contains?(Enum.at(task.content, status_idx), ":") -> |
| 580 | + Enum.at(task.content, status_idx) |
| 581 | + |> String.replace("**Status**:", "") |
| 582 | + |> String.replace("**Status**", "") |
| 583 | + |> String.trim() |
| 584 | + |
| 585 | + # Status is on the next line |
| 586 | + status_idx + 1 < length(task.content) -> |
| 587 | + Enum.at(task.content, status_idx + 1) |
| 588 | + |> String.trim() |
| 589 | + |
| 590 | + true -> |
| 591 | + "MISSING" |
| 449 592 | end |
| 450 593 | |
| 451 594 | if status != "MISSING" && !Enum.member?(@valid_statuses, status) do |
| 452 595 | {:error, "Task #{task.id} has invalid status: #{status}"} |
| 453 596 | else |
| 454 597 | # Extract priority and validate |
| 455 | - priority_line = |
| 456 | - Enum.find(task.content, fn line -> String.starts_with?(line, "**Priority**") end) |
| 598 | + priority_idx = |
| 599 | + Enum.find_index(task.content, fn line -> |
| 600 | + String.starts_with?(line, "**Priority**") |
| 601 | + end) |
| 457 602 | |
| 458 603 | priority = |
| 459 | - if priority_line do |
| 460 | - priority_line |
| 461 | - |> String.replace("**Priority**:", "") |
| 462 | - |> String.replace("**Priority**", "") |
| 463 | - |> String.trim() |
| 464 | - else |
| 465 | - "MISSING" |
| 604 | + cond do |
| 605 | + is_nil(priority_idx) -> |
| 606 | + "MISSING" |
| 607 | + |
| 608 | + # Check if priority is on the same line |
| 609 | + String.contains?(Enum.at(task.content, priority_idx), ":") -> |
| 610 | + Enum.at(task.content, priority_idx) |
| 611 | + |> String.replace("**Priority**:", "") |
| 612 | + |> String.replace("**Priority**", "") |
| 613 | + |> String.trim() |
| 614 | + |
| 615 | + # Priority is on the next line |
| 616 | + priority_idx + 1 < length(task.content) -> |
| 617 | + Enum.at(task.content, priority_idx + 1) |
| 618 | + |> String.trim() |
| 619 | + |
| 620 | + true -> |
| 621 | + "MISSING" |
| 466 622 | end |
| 467 623 | |
| 468 624 | if priority != "MISSING" && !Enum.member?(@valid_priorities, priority) do |
| 469 625 | {:error, "Task #{task.id} has invalid priority: #{priority}"} |
| 470 626 | else |
| 471 | - # Validate subtasks if task is "In Progress" |
| 472 | - if status == "In Progress" && task.subtasks == [] do |
| 473 | - {:error, "Task #{task.id} is in progress but has no subtasks"} |
| 474 | - else |
| 475 | - validate_subtasks(task) |
| 627 | + # Extract and validate dependencies |
| 628 | + dependencies_line = |
| 629 | + Enum.find(task.content, fn line -> |
| 630 | + String.starts_with?(line, "**Dependencies**") |
| 631 | + end) |
| 632 | + |
| 633 | + dependencies = |
| 634 | + if dependencies_line do |
| 635 | + dependencies_line |
| 636 | + |> String.replace("**Dependencies**:", "") |
| 637 | + |> String.replace("**Dependencies**", "") |
| 638 | + |> String.trim() |
| 639 | + else |
| 640 | + "MISSING" |
| 641 | + end |
| 642 | + |
| 643 | + # Validate dependencies if not "None" or missing |
| 644 | + dependency_validation = |
| 645 | + if dependencies != "MISSING" && dependencies != "None" do |
| 646 | + # Parse dependencies (comma-separated task IDs) |
| 647 | + dep_ids = |
| 648 | + dependencies |
| 649 | + |> String.split(",") |
| 650 | + |> Enum.map(&String.trim/1) |
| 651 | + |> Enum.reject(&(&1 == "")) |
| 652 | + |
| 653 | + # Check if all dependencies exist |
| 654 | + invalid_deps = |
| 655 | + Enum.reject(dep_ids, fn dep_id -> |
| 656 | + MapSet.member?(all_task_ids, dep_id) |
| 657 | + end) |
| 658 | + |
| 659 | + if invalid_deps == [] do |
| 660 | + :ok |
| 661 | + else |
| 662 | + {:error, |
| 663 | + "Task #{task.id} has invalid dependencies: #{Enum.join(invalid_deps, ", ")}"} |
| 664 | + end |
| 665 | + else |
| 666 | + :ok |
| 667 | + end |
| 668 | + |
| 669 | + case dependency_validation do |
| 670 | + {:error, reason} -> |
| 671 | + {:error, reason} |
| 672 | + |
| 673 | + :ok -> |
| 674 | + # Validate Code Quality KPIs section |
| 675 | + kpi_validation = validate_code_quality_kpis(task) |
| 676 | + |
| 677 | + case kpi_validation do |
| 678 | + {:error, reason} -> |
| 679 | + {:error, reason} |
| 680 | + |
| 681 | + :ok -> |
| 682 | + # Validate task category |
| 683 | + category_validation = validate_task_category(task) |
| 684 | + |
| 685 | + case category_validation do |
| 686 | + {:error, reason} -> |
| 687 | + {:error, reason} |
| 688 | + |
| 689 | + :ok -> |
| 690 | + # Validate subtasks if task is "In Progress" |
| 691 | + if status == "In Progress" && task.subtasks == [] do |
| 692 | + {:error, "Task #{task.id} is in progress but has no subtasks"} |
| 693 | + else |
| 694 | + validate_subtasks(task) |
| 695 | + end |
| 696 | + end |
| 697 | + end |
| 476 698 | end |
| 477 699 | end |
| 478 700 | end |
| @@ -483,6 +705,137 @@ defmodule TaskValidator do | |
| 483 705 | end |
| 484 706 | end |
| 485 707 | |
| 708 | + defp validate_task_category(task) do |
| 709 | + # Extract numeric part from task ID to determine category |
| 710 | + case Regex.run(~r/^[A-Z]{2,4}(\d{3,4})/, task.id) do |
| 711 | + [_, number_str] -> |
| 712 | + number = String.to_integer(number_str) |
| 713 | + |
| 714 | + # Find which category this number belongs to |
| 715 | + category = |
| 716 | + Enum.find(@category_ranges, fn {_category, {min, max}} -> |
| 717 | + number >= min && number <= max |
| 718 | + end) |
| 719 | + |
| 720 | + case category do |
| 721 | + {category_name, _range} -> |
| 722 | + # Validate that the task has appropriate sections for its category |
| 723 | + validate_category_specific_sections(task, category_name) |
| 724 | + |
| 725 | + nil -> |
| 726 | + {:error, "Task #{task.id} number #{number} doesn't fit any defined category range"} |
| 727 | + end |
| 728 | + |
| 729 | + nil -> |
| 730 | + {:error, "Task #{task.id} has invalid ID format for category validation"} |
| 731 | + end |
| 732 | + end |
| 733 | + |
| 734 | + defp validate_category_specific_sections(task, category_name) do |
| 735 | + # Define category-specific required sections |
| 736 | + category_sections = |
| 737 | + case category_name do |
| 738 | + "core" -> |
| 739 | + ["**Architecture Notes**", "**Complexity Assessment**"] |
| 740 | + |
| 741 | + "features" -> |
| 742 | + ["**Abstraction Evaluation**", "**Simplicity Progression Plan**"] |
| 743 | + |
| 744 | + "documentation" -> |
| 745 | + ["**Content Strategy**", "**Audience Analysis**"] |
| 746 | + |
| 747 | + "testing" -> |
| 748 | + ["**Test Strategy**", "**Coverage Requirements**"] |
| 749 | + |
| 750 | + _ -> |
| 751 | + [] |
| 752 | + end |
| 753 | + |
| 754 | + # Check if all category-specific sections are present |
| 755 | + missing_sections = |
| 756 | + Enum.filter(category_sections, fn section -> |
| 757 | + !Enum.any?(task.content, fn line -> |
| 758 | + String.starts_with?(line, section) |
| 759 | + end) |
| 760 | + end) |
| 761 | + |
| 762 | + if missing_sections == [] do |
| 763 | + :ok |
| 764 | + else |
| 765 | + {:error, |
| 766 | + "Task #{task.id} (#{category_name} category) missing required sections: #{Enum.join(missing_sections, ", ")}"} |
| 767 | + end |
| 768 | + end |
| 769 | + |
| 770 | + defp validate_code_quality_kpis(task) do |
| 771 | + # Find the Code Quality KPIs section |
| 772 | + kpi_section_start = |
| 773 | + Enum.find_index(task.content, fn line -> |
| 774 | + String.starts_with?(line, "**Code Quality KPIs**") |
| 775 | + end) |
| 776 | + |
| 777 | + if kpi_section_start == nil do |
| 778 | + {:error, "Task #{task.id} is missing Code Quality KPIs section"} |
| 779 | + else |
| 780 | + # Extract the KPI section content |
| 781 | + kpi_content = |
| 782 | + task.content |
| 783 | + |> Enum.drop(kpi_section_start + 1) |
| 784 | + |> Enum.take_while(fn line -> |
| 785 | + !String.match?(line, ~r/^\*\*[^*]+\*\*/) && String.trim(line) != "" |
| 786 | + end) |
| 787 | + |> Enum.reject(&(String.trim(&1) == "")) |
| 788 | + |
| 789 | + # Parse KPI values |
| 790 | + kpis = %{ |
| 791 | + functions_per_module: extract_kpi_value(kpi_content, ~r/functions per module:\s*(\d+)/i), |
| 792 | + lines_per_function: extract_kpi_value(kpi_content, ~r/lines per function:\s*(\d+)/i), |
| 793 | + call_depth: extract_kpi_value(kpi_content, ~r/call depth:\s*(\d+)/i) |
| 794 | + } |
| 795 | + |
| 796 | + # Check all KPIs are present |
| 797 | + missing_kpis = |
| 798 | + [:functions_per_module, :lines_per_function, :call_depth] |
| 799 | + |> Enum.filter(fn key -> is_nil(kpis[key]) end) |
| 800 | + |> Enum.map(fn |
| 801 | + :functions_per_module -> "Functions per module" |
| 802 | + :lines_per_function -> "Lines per function" |
| 803 | + :call_depth -> "Call depth" |
| 804 | + end) |
| 805 | + |
| 806 | + if missing_kpis != [] do |
| 807 | + {:error, "Task #{task.id} missing KPIs: #{Enum.join(missing_kpis, ", ")}"} |
| 808 | + else |
| 809 | + # Validate KPI values are within limits |
| 810 | + cond do |
| 811 | + kpis.functions_per_module > @max_functions_per_module -> |
| 812 | + {:error, |
| 813 | + "Task #{task.id} exceeds max functions per module: #{kpis.functions_per_module} > #{@max_functions_per_module}"} |
| 814 | + |
| 815 | + kpis.lines_per_function > @max_lines_per_function -> |
| 816 | + {:error, |
| 817 | + "Task #{task.id} exceeds max lines per function: #{kpis.lines_per_function} > #{@max_lines_per_function}"} |
| 818 | + |
| 819 | + kpis.call_depth > @max_call_depth -> |
| 820 | + {:error, |
| 821 | + "Task #{task.id} exceeds max call depth: #{kpis.call_depth} > #{@max_call_depth}"} |
| 822 | + |
| 823 | + true -> |
| 824 | + :ok |
| 825 | + end |
| 826 | + end |
| 827 | + end |
| 828 | + end |
| 829 | + |
| 830 | + defp extract_kpi_value(content, regex) do |
| 831 | + Enum.find_value(content, fn line -> |
| 832 | + case Regex.run(regex, line) do |
| 833 | + [_, value] -> String.to_integer(value) |
| 834 | + _ -> nil |
| 835 | + end |
| 836 | + end) |
| 837 | + end |
| 838 | + |
| 486 839 | defp validate_subtasks(task) do |
| 487 840 | # Get the task prefix for checking subtask consistency |
| 488 841 | task_prefix = |
| @@ -497,10 +850,17 @@ defmodule TaskValidator do | |
| 497 850 | # Check subtask ID prefix consistency with parent task |
| 498 851 | if task_prefix != nil do |
| 499 852 | subtask_prefix = |
| 500 | - if String.match?(subtask.id, ~r/^[A-Z]{2,4}\d{3,4}-\d+$/) do |
| 501 | - Regex.run(~r/^([A-Z]{2,4})/, subtask.id) |> Enum.at(1) |
| 502 | - else |
| 503 | - nil |
| 853 | + cond do |
| 854 | + # Traditional numbered format (SSH0001-1) |
| 855 | + String.match?(subtask.id, ~r/^[A-Z]{2,4}\d{3,4}-\d+$/) -> |
| 856 | + Regex.run(~r/^([A-Z]{2,4})/, subtask.id) |> Enum.at(1) |
| 857 | + |
| 858 | + # Checkbox format (WNX0019a) |
| 859 | + String.match?(subtask.id, ~r/^[A-Z]{2,4}\d{3,4}[a-z]$/) -> |
| 860 | + Regex.run(~r/^([A-Z]{2,4})/, subtask.id) |> Enum.at(1) |
| 861 | + |
| 862 | + true -> |
| 863 | + nil |
| 504 864 | end |
| 505 865 | |
| 506 866 | if subtask_prefix != nil && subtask_prefix != task_prefix do |
| @@ -515,47 +875,88 @@ defmodule TaskValidator do | |
| 515 875 | |
| 516 876 | # For completed subtasks, check rating |
| 517 877 | subtask_content = |
| 518 | - task.content |
| 519 | - |> Enum.slice(subtask.line..length(task.content)) |
| 520 | - |> Enum.take_while(fn line -> |
| 521 | - !String.match?(line, ~r/^####/) || line == Enum.at(task.content, subtask.line) |
| 522 | - end) |
| 878 | + if Map.get(subtask, :format) == :checkbox do |
| 879 | + # For checkbox format, content is usually on the same line or immediately following |
| 880 | + # Take the checkbox line and any following lines until next checkbox or section |
| 881 | + task.content |
| 882 | + |> Enum.slice(subtask.line..length(task.content)) |
| 883 | + |> Enum.take_while(fn line -> |
| 884 | + (!String.match?(line, ~r/^####/) && !String.match?(line, ~r/^- \[[x\s]\]/)) || |
| 885 | + line == Enum.at(task.content, subtask.line) |
| 886 | + end) |
| 887 | + else |
| 888 | + # Traditional numbered format |
| 889 | + task.content |
| 890 | + |> Enum.slice(subtask.line..length(task.content)) |
| 891 | + |> Enum.take_while(fn line -> |
| 892 | + !String.match?(line, ~r/^####/) || line == Enum.at(task.content, subtask.line) |
| 893 | + end) |
| 894 | + end |
| 523 895 | |
| 524 896 | # Extract status first |
| 525 | - status_line = |
| 526 | - Enum.find(subtask_content, fn line -> String.starts_with?(line, "**Status**") end) |
| 527 | - |
| 528 897 | status = |
| 529 | - if status_line do |
| 530 | - status_line |
| 531 | - |> String.replace("**Status**:", "") |
| 532 | - |> String.replace("**Status**", "") |
| 533 | - |> String.trim() |
| 898 | + if Map.get(subtask, :format) == :checkbox do |
| 899 | + # For checkbox format, checked means completed |
| 900 | + if Map.get(subtask, :checked, false) do |
| 901 | + "Completed" |
| 902 | + else |
| 903 | + "Planned" |
| 904 | + end |
| 534 905 | else |
| 535 | - "MISSING" |
| 906 | + # Traditional format uses Status field |
| 907 | + status_idx = |
| 908 | + Enum.find_index(subtask_content, fn line -> |
| 909 | + String.starts_with?(line, "**Status**") |
| 910 | + end) |
| 911 | + |
| 912 | + cond do |
| 913 | + is_nil(status_idx) -> |
| 914 | + "MISSING" |
| 915 | + |
| 916 | + # Check if status is on the same line |
| 917 | + String.contains?(Enum.at(subtask_content, status_idx), ":") -> |
| 918 | + Enum.at(subtask_content, status_idx) |
| 919 | + |> String.replace("**Status**:", "") |
| 920 | + |> String.replace("**Status**", "") |
| 921 | + |> String.trim() |
| 922 | + |
| 923 | + # Status is on the next line |
| 924 | + status_idx + 1 < length(subtask_content) -> |
| 925 | + Enum.at(subtask_content, status_idx + 1) |
| 926 | + |> String.trim() |
| 927 | + |
| 928 | + true -> |
| 929 | + "MISSING" |
| 930 | + end |
| 536 931 | end |
| 537 932 | |
| 538 933 | # Check if status is valid |
| 539 934 | if status != "MISSING" && !Enum.member?(@valid_statuses, status) do |
| 540 935 | {:halt, {:error, "Subtask #{subtask.id} has invalid status: #{status}"}} |
| 541 936 | else |
| 542 | - # Check if subtask has required sections, including error handling |
| 543 | - # Use simplified error handling format for subtasks |
| 544 | - required_subtask_sections = |
| 545 | - [ |
| 546 | - "**Status**" |
| 547 | - ] ++ @subtask_error_handling_sections |
| 548 | - |
| 937 | + # Check if subtask has required sections |
| 549 938 | missing_sections = |
| 550 | - Enum.filter(required_subtask_sections, fn section -> |
| 551 | - !Enum.any?(subtask_content, fn line -> |
| 552 | - String.starts_with?(line, section) |
| 939 | + if Map.get(subtask, :format) == :checkbox do |
| 940 | + # Checkbox format subtasks don't require separate sections |
| 941 | + # They're typically one-line items |
| 942 | + [] |
| 943 | + else |
| 944 | + # Traditional format requires status and error handling sections |
| 945 | + required_subtask_sections = |
| 946 | + [ |
| 947 | + "**Status**" |
| 948 | + ] ++ @subtask_error_handling_sections |
| 949 | + |
| 950 | + Enum.filter(required_subtask_sections, fn section -> |
| 951 | + !Enum.any?(subtask_content, fn line -> |
| 952 | + String.starts_with?(line, section) |
| 953 | + end) |
| 553 954 | end) |
| 554 | - end) |
| 955 | + end |
| 555 956 | |
| 556 957 | if missing_sections == [] do |
| 557 | - # If completed, check review rating (after confirming no missing sections) |
| 558 | - if status == "Completed" do |
| 958 | + # If completed, check review rating (only for traditional format) |
| 959 | + if status == "Completed" && Map.get(subtask, :format) != :checkbox do |
| 559 960 | rating_line = |
| 560 961 | Enum.find(subtask_content, fn line -> |
| 561 962 | String.starts_with?(line, "**Review Rating**") |
| @@ -4,7 +4,7 @@ defmodule TaskValidator.MixProject do | |
| 4 4 | def project do |
| 5 5 | [ |
| 6 6 | app: :task_validator, |
| 7 | - version: "0.5.0", |
| 7 | + version: "0.6.0", |
| 8 8 | elixir: "~> 1.18", |
| 9 9 | start_permanent: Mix.env() == :prod, |
| 10 10 | description: description(), |
| @@ -33,8 +33,8 @@ defmodule TaskValidator.MixProject do | |
| 33 33 | defp description do |
| 34 34 | """ |
| 35 35 | A library for validating Markdown task lists with structured format specifications. |
| 36 | - Enforces error handling documentation, supports multi-project prefixes (SSH0001, ERR001), |
| 37 | - and ensures consistent task tracking with ID formats and required sections. |
| 36 | + Features: checkbox subtasks, dependencies, code quality KPIs, task categories, |
| 37 | + multi-project prefixes, and comprehensive error handling documentation. |
| 38 38 | """ |
| 39 39 | end |