Agent skill
tools-file-ops
Base module providing reusable file operations patterns for CUI workflow scripts
Install this agent skill to your Project
npx add-skill https://github.com/majiayu000/claude-skill-registry/tree/main/skills/data/tools-file-ops
SKILL.md
File Operations Base Skill
Role: Shared Python module providing atomic file operations, metadata parsing, JSON output helpers, and base directory configuration for CUI workflow scripts.
What This Skill Provides
- Workflow base directory configuration (
.plan/by default) - Path construction helpers for workflow files
- Atomic file write (temp file + rename pattern)
- Directory creation (mkdir -p equivalent)
- JSON success/error output helpers
- Markdown key=value metadata parsing
- Markdown metadata generation
When to Use
Import file_ops module in Python scripts that write to .plan/ directories:
- Lessons learned scripts
- Plan file scripts
- Memory management scripts
- Any script requiring atomic writes to workflow directories
Module: file_ops.py
Location: scripts/file_ops.py
Functions
Base Directory Functions
1. get_base_dir()
- Purpose: Get the base directory for workflow files
- Input: None
- Output:
Path- base directory (default:.plan)
2. set_base_dir(path)
- Purpose: Override the base directory for workflow files
- Input:
path(str/Path) - new base directory - Output: None
- Note: Primarily for testing; production uses
.plandefault
*3. base_path(parts)
- Purpose: Construct a path within the workflow base directory
- Input:
*parts- path components to join - Output:
Path- full path including workflow base directory - Example:
base_path('plans', 'my-task', 'plan.md')→.plan/plans/my-task/plan.md
File Operations
4. atomic_write_file(path, content)
- Purpose: Write file atomically using temp file + rename
- Input:
path(str/Path),content(str) - Output: None (raises on error)
- Pattern: Creates temp file, writes, renames to target
5. ensure_directory(path)
- Purpose: Create directory and parents if needed
- Input:
path(str/Path) - file or directory path - Output: None
- Note: If path looks like file, creates parent directory
JSON Output Helpers
**6. output_success(operation, kwargs)
- Purpose: Print JSON success output to stdout
- Input:
operation(str), additional kwargs - Output: Prints JSON to stdout
7. output_error(operation, error)
- Purpose: Print JSON error output to stderr
- Input:
operation(str),error(str) - Output: Prints JSON to stderr
Metadata Functions
8. parse_markdown_metadata(content)
- Purpose: Parse key=value metadata from markdown
- Input:
content(str) - full file content - Output:
dict- metadata key-value pairs - Format: Supports
key=valueandkey.subkey=value(dot notation)
9. generate_markdown_metadata(data)
- Purpose: Generate key=value metadata block
- Input:
data(dict) - metadata to serialize - Output:
str- formatted metadata block
10. update_markdown_metadata(content, updates)
- Purpose: Update specific metadata fields in markdown content
- Input:
content(str),updates(dict) - Output:
str- updated content
11. get_metadata_content_split(content)
- Purpose: Split markdown content into metadata and body
- Input:
content(str) - Output:
tuple[str, str]- (metadata_block, body_content)
Usage Example
#!/usr/bin/env python3
import sys
sys.path.insert(0, '/path/to/file-operations-base/scripts')
from file_ops import (
atomic_write_file,
base_path,
output_success,
output_error,
generate_markdown_metadata
)
def main():
try:
# Construct path within .plan directory
filepath = base_path('lessons-learned', '2025-11-28-001.md')
# Generate metadata
metadata = generate_markdown_metadata({
'id': '2025-11-28-001',
'component.type': 'command',
'applied': 'false'
})
# Write atomically (creates directories automatically)
content = f"{metadata}\n# Lesson Title\n\nContent here..."
atomic_write_file(filepath, content)
output_success('write-lesson', file=str(filepath))
except Exception as e:
output_error('write-lesson', str(e))
sys.exit(1)
if __name__ == '__main__':
main()
Scripts
| Script | Purpose |
|---|---|
file_ops.py |
Core file operations module (importable) |
test-file-ops.py |
Test suite for file operations |
Integration
With manage-lessons
sys.path.insert(0, str(Path(__file__).parent.parent.parent / 'file-operations-base' / 'scripts'))
from file_ops import atomic_write_file, base_path, output_success, output_error
With plan-files
sys.path.insert(0, str(Path(__file__).resolve().parents[4] / 'plan-marshall' / 'skills' / 'file-operations-base' / 'scripts'))
from file_ops import atomic_write_file, base_path, output_success, output_error
Directory Structure
Files are stored in .plan/ directory:
.plan/ # Workflow artifacts
├── run-configuration.json # Command execution tracking
├── lessons-learned/ # Knowledge capture
│ └── *.md
├── memory/ # Session state
│ ├── context/*.json
│ └── handoffs/*.json
└── plans/ # Task plans
└── {task-name}/
├── plan.md
└── references.toon
Cross-Domain Access Pattern
When scripts in one domain (e.g., pm-workflow:plan-files) need to access resources in another domain (e.g., plan-marshall:manage-lessons), follow the ID-based access pattern.
Principle
Scripts take IDs, not paths, for cross-domain resources. The script resolves the ID to a path internally using base_path().
Why This Matters
- Encapsulation: Each domain owns its file structure; other domains should not construct paths
- Maintainability: Path format changes only require updating the owning domain's script
- Testability: ID-based APIs are easier to mock and test
- Error clarity: Scripts can provide domain-specific error messages for invalid IDs
Correct Pattern
# Script in planning domain needs to access lesson from lessons-learned domain
# CORRECT: Accept ID, resolve path internally
def copy_lesson_to_plan(lesson_id: str, plan_dir: Path) -> dict:
# Resolve ID to path internally
lesson_file = base_path("lessons-learned", f"{lesson_id}.md")
if not lesson_file.exists():
return {"success": False, "error": f"Lesson not found: {lesson_id}"}
# Proceed with copy...
Incorrect Pattern (Anti-Pattern)
# WRONG: Orchestrator constructs path and passes it to script
# In orchestrator (phase-management SKILL.md):
python3 {script} --lesson-file {lesson.file} # BAD: orchestrator builds path
# In script:
def copy_lesson_to_plan(lesson_file: Path, plan_dir: Path): # BAD: accepts path
pass
When to Use ID-Based Access
| Scenario | Use ID-Based | Reason |
|---|---|---|
| Cross-domain resource access | Yes | Scripts own their domain's paths |
| Same-domain resource access | Optional | Same skill owns both paths |
| User-specified file | No | User explicitly provides path |
| Configuration files | No | Paths defined in config are explicit |
Implementation Checklist
When creating scripts that access cross-domain resources:
- Accept resource ID (e.g.,
--lesson-id) not path - Import
base_pathfrom file_ops - Resolve path internally:
base_path("domain-dir", f"{id}.md") - Return clear error if resource not found
- Document the expected ID format in help text
Didn't find tool you were looking for?