AgentSkillsCN

Opsx Render Implementation

Opsx 渲染实现

SKILL.md

Skill: Implement opsx-render Command

Overview

The opsx-render command renders OpenSpec artifact files (spec.md and tasks.md) from the current state of td task management. This is a critical component that enables agents to work in td while keeping the filesystem synchronized.

Architecture

Input

  • Change name (e.g., opsx-render agent-task-management)
  • Change must exist and have proposal, specs, and tasks artifacts

Processing Pipeline

code
1. Load Change Metadata
   ├── Get proposal_feature_id from proposal.md frontmatter
   └── Validate td state has proposal feature/bug

2. Load Spec Metadata
   ├── Read all specs/**/*.md files
   ├── Extract spec_feature_id from each spec's frontmatter
   └── Validate td state has corresponding spec features/bugs

3. Query TD State
   ├── td list --parent <proposal_feature_id>  → all spec features
   ├── For each spec:
   │   └── td list --parent <spec_feature_id> --labels requirement → all requirements
   └── Extract task status (open/in_progress/done)

4. Render spec.md Files
   ├── For each spec file:
   │   ├── Keep existing requirement content (don't regenerate)
   │   ├── Preserve all scenarios and descriptions
   │   ├── Verify all requirement task IDs exist in td
   │   └── Update frontmatter with spec_feature_id
   └── Write to specs/<capability>/spec.md

5. Render tasks.md
   ├── Group requirement tasks by parent spec
   ├── Create section for each spec
   │   ├── Section name and status (Not Started/In Progress/Done)
   │   ├── List each requirement task with checkbox state
   │   └── Calculate phase status from task states
   └── Write summary and progress information

6. Stage Files (Optional)
   └── git add openspec/changes/<change>/specs/**/*.md openspec/changes/<change>/tasks.md

Key Design Decisions

  1. Specs are NOT regenerated: The spec.md content comes from the files themselves, not from td. Rendering only updates frontmatter and preserves all content.

  2. Requirement IDs are preserved: Task IDs in requirement markdown are read from the spec files, not regenerated. This maintains traceability.

  3. Task status comes from td: Only the checkbox state [ ] vs [x] in tasks.md changes based on td state. Everything else in tasks.md is preserved.

  4. Idempotent operation: Running render multiple times without td changes produces identical output. No file timestamp churn.

  5. Atomic writes: All files are written atomically - no partial updates. If any file fails, the entire operation fails and files remain unchanged.

Implementation Requirements

Requirement 1: Generate spec.md from td state (td-87cb57)

Acceptance Criteria:

  • ✅ Read spec.md frontmatter to get spec_feature_id
  • ✅ Verify spec_feature_id exists in td
  • ✅ Keep all requirement content from existing spec.md
  • ✅ Verify requirement task IDs exist in td
  • ✅ Output spec.md with preserved content

Implementation Details:

  1. Parse spec.md frontmatter YAML
  2. Extract spec_feature_id
  3. Query td: td show <spec_feature_id> to verify it exists
  4. Read entire spec.md content
  5. For each requirement section:
    • Extract the requirement name and td-id from markdown
    • Query td: td show <td-id> to verify it exists and has label=requirement
    • Keep all content between requirement header and next section
  6. Write updated spec.md with same content but current frontmatter

Scenarios:

  • Spec with valid td ids: render succeeds, file unchanged
  • Spec with missing td id: error message explaining which task is missing
  • Spec with corrupted frontmatter: error with hint to run /opsx-continue

Requirement 2: Generate tasks.md from td state (td-65f257)

Acceptance Criteria:

  • ✅ Group requirement tasks by parent spec feature
  • ✅ Show task status from td (open/in_progress/done)
  • ✅ Create checkbox entries from task status
  • ✅ Calculate phase completion percentage
  • ✅ Output tasks.md with correct grouping and status

Implementation Details:

  1. For each spec feature (queried from td):

    • Get spec name and feature id
    • Create a section header with spec name
  2. For each requirement task under that spec:

    • Get task status from td
    • Map: open → [ ], in_progress/done → [x]
    • Include task ID in description
    • Include requirement title from task description
  3. Calculate phase status:

    • If all tasks done: "Done"
    • If any tasks in_progress: "In Progress"
    • If all tasks open: "Not Started"
    • If mixed: "In Progress"
  4. Include summary section:

    • Total requirements
    • Complete count
    • Remaining count
    • Per-phase breakdown

Scenarios:

  • No requirements: tasks.md shows 0 total tasks with guidance
  • Mixed completion: tasks.md shows correct checkbox state for each
  • New phase (no requirements yet): phase section shows "Not Started"

Requirement 3: Preserve task IDs and links (td-b16425)

Acceptance Criteria:

  • ✅ Every requirement in spec.md includes its td task ID
  • ✅ Every task in tasks.md includes its td task ID
  • ✅ IDs are in consistent format (td-xxxxxx)
  • ✅ IDs match actual td tasks
  • ✅ Traceability maintained across files

Implementation Details:

  1. In spec.md rendering:

    • Extract td-id from requirement markdown
    • Verify format: matches td-[a-f0-9]{6}
    • Keep in requirement header: ### Requirement: <name> - td-xxxxx
  2. In tasks.md rendering:

    • Include td-id in task description/comment
    • Format: - [x] td-xxxxx: requirement name
    • Maintain consistent placement (always after checkbox)
  3. Validation:

    • Every requirement must have a td-id in spec.md
    • Every task checkbox must have a td-id in tasks.md
    • All ids must exist in td and have label=requirement

Scenarios:

  • Valid ids: render preserves them exactly as-is
  • Missing id in spec: error with clear message
  • Mismatched id (doesn't exist in td): error with hint to check td

Requirement 4: Follow openspec templates (td-13ec31)

Acceptance Criteria:

  • ✅ Generated spec.md matches openspec template structure
  • ✅ Generated tasks.md matches openspec template structure
  • ✅ Frontmatter is valid YAML
  • ✅ Markdown formatting is consistent
  • ✅ Sections use correct heading levels

Implementation Details:

  1. spec.md template structure:

    code
    ---
    spec_feature_id: td-xxxxx
    ---
    
    # Specification: [Name]
    
    ## Overview
    [existing content]
    
    ## ADDED Requirements
    [existing content]
    
    ## MODIFIED Requirements
    [existing content]
    
    ## REMOVED Requirements
    [existing content]
    
  2. tasks.md template structure:

    code
    # Tasks
    
    ## 1. [Spec Name]
    - Status: [Not Started/In Progress/Done]
    - Type: Spec Feature (td-xxxxx)
    - Assignee: [from tasks.md]
    - Label: [from tasks.md]
    - Tasks:
      - [x] td-xxxxx: requirement name
      - [ ] td-xxxxx: requirement name
    
    ## Summary
    [progress information]
    
  3. Validation:

    • All required sections present
    • Frontmatter is valid YAML (parseable)
    • Headings use correct levels (# ## ###)
    • Markdown is valid and renders correctly

Scenarios:

  • Valid structure: render succeeds
  • Missing frontmatter: add it
  • Malformed YAML: error with hint about frontmatter

Requirement 5: Handle edge cases gracefully (td-509158)

Acceptance Criteria:

  • ✅ Missing spec feature: clear error message
  • ✅ Missing requirement task: clear error message
  • ✅ Corrupted td state: diagnostic message
  • ✅ No partial writes: atomicity guaranteed
  • ✅ All edge cases fail safely

Implementation Details:

  1. Error handling:

    • Missing spec_feature_id → "spec.md missing spec_feature_id. Run: td create -t feature..."
    • Missing requirement task → "Requirement task td-xxxxx not found in td. Create it with: td create..."
    • Corrupted td data → "Failed to query td for spec feature td-xxxxx: [error details]"
    • Filesystem error → "Failed to write specs/foo/spec.md: [error details]"
  2. Atomicity:

    • Don't update files one-by-one
    • Render all in-memory first
    • Validate all files can be written
    • Then write all files together
    • If any write fails, leave filesystem unchanged
  3. Recovery:

    • Don't delete original files on error
    • Leave originals intact, show error
    • Agent can fix issue and retry
  4. Special cases:

    • First time rendering (files don't exist): create them
    • Only proposal/design changed (no specs): skip rendering, success
    • No td changes (state unchanged): render succeeds, files unchanged
    • Partial specs (some have td ids, some don't): error identifying missing ids

Scenarios:

  • First render: creates all spec and tasks files
  • Missing required ids: error with actionable fix
  • No changes: succeeds, files unchanged (idempotent)
  • Filesystem full: error, original files preserved

Integration with Pre-Commit Hooks

The opsx-render command is invoked automatically by the pre-commit hook when files in openspec/changes/* change:

bash
.git/hooks/pre-commit:
  if [[ changed_files contain openspec/changes/* ]]; then
    change_name=$(extract_change_name_from_path)
    opsx-render "$change_name"
    git add openspec/changes/$change_name/specs/**/*.md openspec/changes/$change_name/tasks.md
  fi

This ensures documents are always in sync with td state at commit boundaries.

Testing Strategy

Unit Tests

  • Test each spec.md rendering scenario
  • Test tasks.md grouping logic
  • Test td query parsing
  • Test YAML frontmatter handling
  • Test edge case error messages

Integration Tests

  • Test with real td state (create change, specs, requirements, then render)
  • Test idempotency (render twice, files identical)
  • Test atomicity (simulate write failure, verify no partial updates)
  • Test with agent-task-management change (real-world data)

Smoke Tests

  • Create new change, specs, requirements
  • Run opsx-render
  • Verify spec.md and tasks.md are correct
  • Modify requirement status in td
  • Run opsx-render again
  • Verify tasks.md checkbox state changed

Next Steps

  1. Implement opsx-render command in CLI
  2. Add to openspec command registry
  3. Integrate with pre-commit hook
  4. Test with agent-task-management change
  5. Move to opsx-* commands enhancement (Phase 3)