Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
62 changes: 34 additions & 28 deletions docuchango/cli.py
Original file line number Diff line number Diff line change
Expand Up @@ -797,11 +797,15 @@ def migrate(
\b
- Adds missing 'project_id' field
- Generates 'doc_uuid' (UUID v4) if missing
- Migrates legacy 'date' field to 'created'/'updated'
- Adds 'created'/'updated' from git history if missing
- Migrates legacy 'date' field to 'created'
- Adds 'created' from git history if missing
- Removes deprecated/derived fields ('updated', 'date')
- Normalizes 'id' field to lowercase format
- Normalizes tags to lowercase with hyphens

Note: The 'updated' field is removed because it can be derived from
git history. Use 'docuchango bulk timestamps' to compute it on demand.

Examples:

\b
Expand All @@ -819,7 +823,7 @@ def migrate(
Agent instructions to generate required fields:

\b
# Generate created/updated datetime (ISO 8601 UTC):
# Generate created datetime (ISO 8601 UTC):
python -c "from datetime import datetime, timezone; print(datetime.now(timezone.utc).strftime('%Y-%m-%dT%H:%M:%SZ'))"
# Or: date -u +%Y-%m-%dT%H:%M:%SZ

Expand Down Expand Up @@ -934,34 +938,36 @@ def migrate(
changes.append(f"Generated doc_uuid: {new_uuid}")
modified = True

# 3. Migrate legacy 'date' field to 'created'/'updated'
if "date" in post.metadata and "created" not in post.metadata:
date_val = post.metadata["date"]
date_str = date_val.strftime("%Y-%m-%d") if hasattr(date_val, "strftime") else str(date_val)

# Get git dates for updated field
created_date, updated_date = get_git_dates(file_path)

post.metadata["created"] = date_str
post.metadata["updated"] = updated_date or date_str
# 3. Remove legacy 'date' field (will get created from git)
if "date" in post.metadata:
del post.metadata["date"]
changes.append(f"Migrated date → created: {date_str}, updated: {updated_date or date_str}")
changes.append("Removed deprecated 'date' field")
modified = True
# Also remove created if it exists so it gets refreshed from git
if "created" in post.metadata:
del post.metadata["created"]

# 4. Add or update created from git (ensures datetime format)
created_datetime, _ = get_git_dates(file_path)
if created_datetime:
old_created = post.metadata.get("created")
# Normalize to datetime format from git
if old_created != created_datetime:
old_val = str(old_created) if old_created else "None"
post.metadata["created"] = created_datetime
if old_created:
changes.append(f"Normalized created: {old_val} → {created_datetime}")
else:
changes.append(f"Added created: {created_datetime} (from git)")
modified = True

# 4. Add created/updated from git if missing
if "created" not in post.metadata or "updated" not in post.metadata:
created_date, updated_date = get_git_dates(file_path)
if created_date:
if "created" not in post.metadata:
post.metadata["created"] = created_date
changes.append(f"Added created: {created_date} (from git)")
modified = True
if "updated" not in post.metadata:
post.metadata["updated"] = updated_date
changes.append(f"Added updated: {updated_date} (from git)")
modified = True
# 5. Remove 'updated' field (derived from git history)
if "updated" in post.metadata:
del post.metadata["updated"]
changes.append("Removed 'updated' field (derived from git)")
modified = True

# 5. Normalize id field to lowercase
# 6. Normalize id field to lowercase
if "id" in post.metadata:
old_id = post.metadata["id"]
new_id = old_id.lower()
Expand All @@ -980,7 +986,7 @@ def migrate(
changes.append(f"Generated id: {new_id}")
modified = True

# 6. Normalize tags
# 7. Normalize tags
if "tags" in post.metadata:
old_tags = post.metadata["tags"]
if isinstance(old_tags, str):
Expand Down
32 changes: 12 additions & 20 deletions docuchango/schemas.py
Original file line number Diff line number Diff line change
Expand Up @@ -185,13 +185,15 @@ class ADRFrontmatter(BaseModel):
- title: Title without ADR prefix (e.g., "Use Rust for Proxy"). ID displayed by sidebar.
- status: Current state (Proposed/Accepted/Implemented/Deprecated/Superseded)
- created: Date ADR was first created in ISO 8601 format (YYYY-MM-DD)
- updated: Date ADR was last modified in ISO 8601 format (YYYY-MM-DD)
- deciders: Person or team who made the decision (e.g., "Core Team", "Platform Team")
- tags: List of lowercase hyphenated tags for categorization
- id: Lowercase identifier matching filename (e.g., "adr-001" for ADR-001-rust-proxy.md)
- project_id: Project identifier from docs-project.yaml (e.g., "my-project")
- doc_uuid: Unique identifier for backend tracking (UUID v4 format)

DERIVED FIELDS (computed from git history):
- updated: Last modification date, derived from git commit history

DEPRECATED FIELDS (supported for backwards compatibility):
- date: Legacy field, use 'created' instead. Will be auto-migrated.
"""
Expand All @@ -209,10 +211,6 @@ class ADRFrontmatter(BaseModel):
...,
description="DateTime ADR was first created in ISO 8601 format (YYYY-MM-DDTHH:MM:SSZ). Do not change after initial creation",
)
updated: datetime.datetime | datetime.date | str = Field(
...,
description="DateTime ADR was last modified in ISO 8601 format (YYYY-MM-DDTHH:MM:SSZ). Update whenever content changes",
)
deciders: str = Field(
..., description="Who made the decision. Use team name (e.g., 'Core Team') or individual name"
)
Expand Down Expand Up @@ -280,11 +278,13 @@ class RFCFrontmatter(BaseModel):
- status: Current state (Draft/Proposed/Accepted/Implemented/Rejected)
- author: Document author (person or team who wrote the RFC)
- created: Date RFC was first created in ISO 8601 format (YYYY-MM-DD)
- updated: Date RFC was last modified in ISO 8601 format (YYYY-MM-DD)
- tags: List of lowercase hyphenated tags for categorization
- id: Lowercase identifier matching filename (e.g., "rfc-015" for RFC-015-plugin-architecture.md)
- project_id: Project identifier from docs-project.yaml (e.g., "my-project")
- doc_uuid: Unique identifier for backend tracking (UUID v4 format)

DERIVED FIELDS (computed from git history):
- updated: Last modification date, derived from git commit history
"""

title: str = Field(
Expand All @@ -302,10 +302,6 @@ class RFCFrontmatter(BaseModel):
...,
description="DateTime RFC was first created in ISO 8601 format (YYYY-MM-DDTHH:MM:SSZ). Do not change after initial creation",
)
updated: datetime.datetime | datetime.date | str | None = Field(
None,
description="DateTime RFC was last modified in ISO 8601 format (YYYY-MM-DDTHH:MM:SSZ). Update whenever content changes",
)
tags: list[str] = Field(
default_factory=list, description="List of lowercase, hyphenated tags (e.g., ['design', 'api', 'backend'])"
)
Expand Down Expand Up @@ -368,11 +364,13 @@ class MemoFrontmatter(BaseModel):
- title: Title without MEMO prefix (e.g., "Load Test Results"). ID displayed by sidebar.
- author: Document author (person or team who wrote the memo)
- created: Date memo was first created in ISO 8601 format (YYYY-MM-DD)
- updated: Date memo was last modified in ISO 8601 format (YYYY-MM-DD)
- tags: List of lowercase hyphenated tags for categorization
- id: Lowercase identifier matching filename (e.g., "memo-010" for MEMO-010-loadtest-results.md)
- project_id: Project identifier from docs-project.yaml (e.g., "my-project")
- doc_uuid: Unique identifier for backend tracking (UUID v4 format)

DERIVED FIELDS (computed from git history):
- updated: Last modification date, derived from git commit history
"""

title: str = Field(
Expand All @@ -385,10 +383,6 @@ class MemoFrontmatter(BaseModel):
...,
description="DateTime memo was first created in ISO 8601 format (YYYY-MM-DDTHH:MM:SSZ). Do not change after initial creation",
)
updated: datetime.datetime | datetime.date | str = Field(
...,
description="DateTime memo was last modified in ISO 8601 format (YYYY-MM-DDTHH:MM:SSZ). Update whenever content changes",
)
tags: list[str] = Field(
default_factory=list,
description="List of lowercase, hyphenated tags (e.g., ['implementation', 'testing', 'performance'])",
Expand Down Expand Up @@ -453,12 +447,14 @@ class PRDFrontmatter(BaseModel):
- status: Current state (Draft/In Review/Approved/In Progress/Completed/Cancelled)
- author: Document author (person or team who wrote the PRD)
- created: Date PRD was first created in ISO 8601 format (YYYY-MM-DD)
- updated: Date PRD was last modified in ISO 8601 format (YYYY-MM-DD)
- target_release: Target release version or date (e.g., "v2.0.0" or "Q2 2025")
- tags: List of lowercase hyphenated tags for categorization
- id: Lowercase identifier matching filename (e.g., "prd-005" for prd-005-user-auth.md)
- project_id: Project identifier from docs-project.yaml (e.g., "my-project")
- doc_uuid: Unique identifier for backend tracking (UUID v4 format)

DERIVED FIELDS (computed from git history):
- updated: Last modification date, derived from git commit history
"""

title: str = Field(
Expand All @@ -477,10 +473,6 @@ class PRDFrontmatter(BaseModel):
...,
description="DateTime PRD was first created in ISO 8601 format (YYYY-MM-DDTHH:MM:SSZ). Do not change after initial creation",
)
updated: datetime.datetime | datetime.date | str = Field(
...,
description="DateTime PRD was last modified in ISO 8601 format (YYYY-MM-DDTHH:MM:SSZ). Update whenever content changes",
)
target_release: str = Field(
...,
description="Target release version or date (e.g., 'v2.0.0', 'Q2 2025', '2025-06-01')",
Expand Down
Loading