292 lines
6.2 KiB
Markdown
292 lines
6.2 KiB
Markdown
# Introduction
|
||
|
||
This document defines the canonical format, semantics, and processing rules
|
||
for the repository-root `TODO` file. The file is a human-friendly,
|
||
append-only (allowing modifications) issue tracker that is parsed by TypeScript tooling using a MIME
|
||
envelope added at parse time.
|
||
|
||
The raw `TODO` file is not a MIME document. A preprocessor wraps it into a
|
||
valid `multipart/mixed` MIME message before parsing.
|
||
|
||
## Raw File Structure
|
||
|
||
The raw `TODO` file consists of a sequence of *parts*, each beginning with:
|
||
|
||
```default
|
||
--ISSUE
|
||
Content-Type: <mime-type>
|
||
```
|
||
|
||
Valid part types are:
|
||
|
||
* `application/sprints` — defines sprint metadata
|
||
* `application/issue` — defines a single issue
|
||
|
||
Parts may appear in any order in the raw file. The preprocessor will reorder
|
||
them so that the `application/sprints` part appears first.
|
||
|
||
## MIME Envelope (Added by Preprocessor)
|
||
|
||
Before parsing, the preprocessor wraps the raw file into a MIME multipart
|
||
message.
|
||
|
||
Prolog added by the preprocessor:
|
||
|
||
```default
|
||
MIME-Version: 1.0
|
||
Content-Type: multipart/mixed; boundary="ISSUE"
|
||
```
|
||
|
||
Each raw `--ISSUE` boundary becomes a MIME boundary. The preprocessor
|
||
extracts each part’s `Content-Type` and body, reorders parts, and emits a
|
||
final closing boundary:
|
||
|
||
```default
|
||
--ISSUE--
|
||
```
|
||
|
||
The resulting MIME message is parsed using a standard MIME parser.
|
||
|
||
## Part: `application/sprints`
|
||
|
||
This part defines sprint metadata.
|
||
|
||
The body MUST begin with:
|
||
|
||
```default
|
||
Sprints:
|
||
```
|
||
|
||
followed by one or more sprint entries.
|
||
|
||
### Sprint Entry Forms
|
||
|
||
Both compact and expanded forms are valid.
|
||
|
||
Compact form:
|
||
|
||
```default
|
||
- Name: Sprint 1
|
||
Range: 2026-02-01..2026-02-14
|
||
```
|
||
|
||
Expanded form:
|
||
|
||
```default
|
||
-
|
||
Name: Sprint 1
|
||
Range: 2026-02-01..2026-02-14
|
||
```
|
||
|
||
### Sprint Entry Grammar
|
||
|
||
A sprint entry begins at a line matching:
|
||
|
||
```default
|
||
^\s*-\s*(Name:.*)?$
|
||
```
|
||
|
||
Key–value lines inside an entry match:
|
||
|
||
```default
|
||
^\s+[A-Za-z][A-Za-z0-9]*:\s*(.*)$
|
||
```
|
||
|
||
Required keys:
|
||
|
||
* `Name: <string>`
|
||
* `Range: <YYYY-MM-DD>..<YYYY-MM-DD>`
|
||
|
||
The `Range` defines a closed interval `[start, end]`.
|
||
|
||
## Part: `application/issue`
|
||
|
||
Each issue is represented as a structured block with strict field ordering.
|
||
|
||
### Required Field Order
|
||
|
||
The following fields MUST appear in exactly this order:
|
||
|
||
```default
|
||
ID: <integer>
|
||
Type: <feature|bugfix|hotfix>
|
||
Title: <short title>
|
||
Status: <open|in-progress|done|hold|cancelled>
|
||
Priority: <low|medium|high>
|
||
Created: <YYYY-MM-DD>
|
||
Relationships: <comma-separated list or empty>
|
||
DueStart: <YYYY-MM-DD> # OPTIONAL
|
||
DueEnd: <YYYY-MM-DD> # OPTIONAL
|
||
Description: <first line>
|
||
<continuation lines>
|
||
```
|
||
|
||
### Field Semantics
|
||
|
||
* **ID**: unique integer, strictly increasing.
|
||
* **Type**: one of `feature`, `bugfix`, `hotfix`.
|
||
\* `feature` and `bugfix` issues target the integration branch (`develop`).
|
||
\* `hotfix` issues target the stable branch (`main` or `master`).
|
||
* **Status**: one of `open`, `in-progress`, `done`, `hold`, `cancelled`.
|
||
* **Priority**: one of `low`, `medium`, `high`.
|
||
* **Created**: ISO date.
|
||
* **Relationships**:
|
||
\* Empty:
|
||
```default
|
||
Relationships:
|
||
```
|
||
|
||
* Or list:
|
||
```default
|
||
Relationships: dependsOn:43, relatesTo:10
|
||
```
|
||
* Valid kinds: `dependsOn`, `relatesTo`, `blocks`, `causedBy`.
|
||
* **DueStart / DueEnd**:
|
||
\* Optional.
|
||
\* If only one is present, the other is implicitly equal to it.
|
||
|
||
### Description Block
|
||
|
||
* The first line appears on the same line as `Description:`.
|
||
* Continuation lines:
|
||
\* Must begin with whitespace.
|
||
\* Must align indentation consistently.
|
||
* No blank lines allowed inside the description block.
|
||
|
||
### Body
|
||
|
||
Any lines after the description block are considered free-form body text.
|
||
The body MUST NOT contain another `ID:` field or a MIME boundary.
|
||
|
||
## Git Workflow Rules
|
||
|
||
### Branch Naming
|
||
|
||
Branches for issues MUST follow:
|
||
|
||
```default
|
||
<Type>/<ID>
|
||
```
|
||
|
||
Examples:
|
||
|
||
```default
|
||
feature/12
|
||
bugfix/7
|
||
```
|
||
|
||
### Modification Rules
|
||
|
||
* All edits to `TODO` MUST occur on `develop`.
|
||
* Feature/bugfix/hotfix branches MUST rebase changes from `develop`.
|
||
|
||
### Branch Lifecycle
|
||
|
||
* A branch MUST NOT exist before its issue is created.
|
||
* A branch MUST be named exactly `<Type>/<ID>`.
|
||
* A branch MAY merge only when the issue status is `done`.
|
||
|
||
## Sprint Membership Logic
|
||
|
||
Given:
|
||
|
||
* Sprint interval `[S_start, S_end]`
|
||
* Issue interval `[I_start, I_end]`
|
||
|
||
### Normalization
|
||
|
||
* Only `DueEnd` → `I_start = I_end = DueEnd`
|
||
* Only `DueStart` → `I_start = I_end = DueStart`
|
||
* Neither → issue is not sprint-bound
|
||
|
||
### Membership Condition
|
||
|
||
An issue belongs to a sprint if:
|
||
|
||
```default
|
||
I_start ≤ S_end AND I_end ≥ S_start
|
||
```
|
||
|
||
### Current Sprint for Date D
|
||
|
||
1. All sprints where `S_start ≤ D ≤ S_end`.
|
||
2. If multiple match, choose the one with the latest `S_start`.
|
||
3. If none match, no active sprint.
|
||
|
||
## Preprocessor Requirements
|
||
|
||
The preprocessor MUST:
|
||
|
||
* Read the raw `TODO` file.
|
||
* Split into parts using `--ISSUE`.
|
||
* Extract each part’s `Content-Type` and body.
|
||
* Reorder parts so that:
|
||
\* `application/sprints` appears first (if present)
|
||
\* All `application/issue` parts follow in original order
|
||
* Emit a MIME message with:
|
||
\* `MIME-Version: 1.0`
|
||
\* `Content-Type: multipart/mixed; boundary="ISSUE"`
|
||
\* Each part wrapped as:
|
||
```default
|
||
--ISSUE
|
||
Content-Type: <type>
|
||
|
||
<body>
|
||
```
|
||
|
||
* Final boundary:
|
||
```default
|
||
--ISSUE--
|
||
```
|
||
|
||
## Parser Requirements
|
||
|
||
The parser MUST:
|
||
|
||
* Use a MIME parser to extract parts.
|
||
* Dispatch based on `Content-Type`:
|
||
\* `application/sprints` → sprint parser
|
||
\* `application/issue` → issue parser
|
||
* Enforce:
|
||
\* Field order
|
||
\* Required fields
|
||
\* Description indentation rules
|
||
\* Sprint entry grammar
|
||
* Produce a `TodoFile` object:
|
||
```default
|
||
{
|
||
sprints: Sprint[],
|
||
issues: Issue[]
|
||
}
|
||
```
|
||
|
||
## Error Handling
|
||
|
||
The parser MUST reject:
|
||
|
||
* Missing or malformed `Content-Type` headers
|
||
* Unknown MIME types
|
||
* Missing required issue fields
|
||
* Incorrect field order
|
||
* Invalid sprint ranges
|
||
* Invalid date formats
|
||
* Multiple `application/sprints` parts
|
||
* Duplicate issue IDs
|
||
|
||
Errors SHOULD include line numbers when possible.
|
||
|
||
## Extensibility
|
||
|
||
Future part types MAY be added using:
|
||
|
||
```default
|
||
application/<subtype>
|
||
```
|
||
|
||
Examples:
|
||
|
||
* `application/metadata`
|
||
* `application/changelog`
|
||
* `application/epilog`
|
||
|
||
The preprocessor MUST preserve unknown part types but MUST NOT reorder them.
|