skills/workflow-manifest/SKILL.md
--- name: workflow-manifest user-invocable: false allowed-tools: Bash(node .claude/skills/workflow-manifest/scripts/manifest.cjs) --- # Workflow Manifest CLI tool for reading and writing work unit manifest files. Single source of truth for all workflow state. **`{work_unit}`** is the top-level directory name under `.workflows/` (e.g., `dark-mode`, `payments-overhaul`). **`{topic}`** is the item within a phase (e.g., discussion name, spec name, plan name). For feature/bugfix they share the sam
npx skillsauth add leeovery/claude-technical-workflows workflow-manifestInstall this skill globally with one command. Works with Claude Code, Cursor, and Windsurf.
3 of 9 scanners reported clean
Some scanners were skipped, did not run, or reported a non-clean status. Review each row below.
CLI tool for reading and writing work unit manifest files. Single source of truth for all workflow state.
{work_unit} is the top-level directory name under .workflows/ (e.g., dark-mode, payments-overhaul). {topic} is the item within a phase (e.g., discussion name, spec name, plan name). For feature/bugfix they share the same value; for epic they're distinct.
node .claude/skills/workflow-manifest/scripts/manifest.cjs <command> [args]
Every command follows: command path [field] [value]
The path joins work unit, phase, and topic with dots. The field is always a separate argument. Segment count in the path determines the access level:
| Segments | Level | Path | Field | Resolves to |
|----------|-------|------|-------|-------------|
| 1 | Work unit | my-epic | work_type | work_type |
| 2 | Phase | my-epic.planning | format | phases.planning.format |
| 3 | Topic | my-epic.discussion.auth-flow | status | phases.discussion.items.auth-flow.status |
The reserved path prefix project routes to the project manifest (.workflows/manifest.json). For project paths, the field is embedded in the dot-path — no separate field argument:
MANIFEST="node .claude/skills/workflow-manifest/scripts/manifest.cjs"
# Read:
$MANIFEST get project # Full project manifest
$MANIFEST get project.work_units # All work units
$MANIFEST get project.defaults.plan_format # Specific default
# Write (field path + value):
$MANIFEST set project.defaults.plan_format {format}
$MANIFEST push project.defaults.project_skills ".claude/skills/golang-pro"
# Check existence:
$MANIFEST exists project.defaults.plan_format
# Delete:
$MANIFEST delete project.defaults.plan_format
MANIFEST="node .claude/skills/workflow-manifest/scripts/manifest.cjs"
# Work-unit level (1 segment):
$MANIFEST get {work_unit} [field]
$MANIFEST set {work_unit} field value
$MANIFEST delete {work_unit} field.path
$MANIFEST exists {work_unit} [field.path]
# Phase level (2 segments):
$MANIFEST get {work_unit}.{phase} [field]
$MANIFEST set {work_unit}.{phase} field value
$MANIFEST delete {work_unit}.{phase} field.path
# Topic level (3 segments):
$MANIFEST get {work_unit}.{phase}.{topic} [field.path]
$MANIFEST set {work_unit}.{phase}.{topic} field.path value
$MANIFEST delete {work_unit}.{phase}.{topic} field.path
$MANIFEST init-phase {work_unit}.{phase}.{topic}
# Wildcard (3 segments, * as topic):
$MANIFEST get {work_unit}.{phase}.* [field.path]
$MANIFEST exists {work_unit}.{phase}.* [field.path]
# Existence checks (always exit 0, outputs true/false):
$MANIFEST exists {work_unit}
$MANIFEST exists {work_unit} [field.path]
$MANIFEST exists {work_unit}.{phase} [field.path]
$MANIFEST exists {work_unit}.{phase}.{topic} [field.path]
# Management (unchanged):
$MANIFEST init name --work-type type --description "..."
$MANIFEST list [--status s] [--work-type t]
Phase-level access (2-segment path) — accesses fields directly on the phase object (phases.{phase}.{field}). Useful for phase-wide metadata like format, analysis_cache, etc.
Topic-level access (3-segment path) — routes through items: phases.{phase}.items.{topic}.{field}. Used for per-topic state like status, gate modes, etc.
Wildcard topic (* as third segment) — collects values from all topics in a phase. Supported by get and exists only. For epic: iterates all items. For feature/bugfix: returns the single item.
Internal routing (CLI handles, skills don't know):
my-epic.discussion.auth-flow status → phases.discussion.items.auth-flow.statusdiscovery, research, discussion, investigation, specification, planning, implementation, review)project is reserved for project-level manifest accessinitCreate a new work unit manifest.
node .claude/skills/workflow-manifest/scripts/manifest.cjs init <name> --work-type <epic|feature|bugfix|quick-fix|cross-cutting> --description "..."
Creates .workflows/<name>/manifest.json with identity fields and empty phases. Errors if manifest already exists. Rejects names containing dots or matching phase names.
getRead a value or subtree. Three levels:
Work-unit level (1 segment):
# Full manifest
node .claude/skills/workflow-manifest/scripts/manifest.cjs get <name>
# Scalar value — output raw (no quotes)
node .claude/skills/workflow-manifest/scripts/manifest.cjs get <name> status
# Subtree — output as formatted JSON
node .claude/skills/workflow-manifest/scripts/manifest.cjs get <name> phases
Phase level (2 segments):
# Whole phase object
node .claude/skills/workflow-manifest/scripts/manifest.cjs get <name>.discussion
# Specific field within phase
node .claude/skills/workflow-manifest/scripts/manifest.cjs get <name>.planning format
Topic level (3 segments):
# Specific field within topic
node .claude/skills/workflow-manifest/scripts/manifest.cjs get <name>.discussion.auth-flow status
# Nested field path
node .claude/skills/workflow-manifest/scripts/manifest.cjs get <name>.specification.auth-flow sources.auth-api.status
Wildcard topic (3 segments, * as topic):
# Collect status from all topics in a phase
node .claude/skills/workflow-manifest/scripts/manifest.cjs get '<name>.discussion.*' status
Output is a JSON array of {topic, value} objects:
[
{ "topic": "auth-flow", "value": "completed" },
{ "topic": "data-model", "value": "in-progress" }
]
For feature/bugfix, returns a single-element array (topic matches work unit name). Empty stdout if the phase has no items.
Missing paths return empty stdout with exit 0 — covers missing work units, missing fields, and wildcard with no matches. Use exists when you need to distinguish a missing path from a present-but-empty value.
setWrite a value. Three levels:
Work-unit level (1 segment):
node .claude/skills/workflow-manifest/scripts/manifest.cjs set <name> description "Updated description"
node .claude/skills/workflow-manifest/scripts/manifest.cjs set <name> status completed
Phase level (2 segments):
node .claude/skills/workflow-manifest/scripts/manifest.cjs set <name>.planning format {format}
node .claude/skills/workflow-manifest/scripts/manifest.cjs set <name>.research analysis_cache '{"checksum":"..."}'
Topic level (3 segments):
node .claude/skills/workflow-manifest/scripts/manifest.cjs set <name>.discussion.auth-flow status completed
node .claude/skills/workflow-manifest/scripts/manifest.cjs set <name>.planning.auth-flow task_list_gate_mode auto
Values are parsed as JSON first (for arrays, objects, numbers, booleans), falling back to string. Validates structural fields:
epic, feature, bugfix, quick-fix, cross-cuttingdiscovery, research, discussion, investigation, scoping, specification, planning, implementation, reviewgated, autoin-progress, completed, cancelleddeleteRemove a key from the manifest. Three levels:
Work-unit level (1 segment):
node .claude/skills/workflow-manifest/scripts/manifest.cjs delete <name> <field.path>
Phase level (2 segments):
node .claude/skills/workflow-manifest/scripts/manifest.cjs delete <name>.research analysis_cache
Topic level (3 segments):
node .claude/skills/workflow-manifest/scripts/manifest.cjs delete <name>.implementation.auth completed_tasks
Errors if the path does not exist. Deletes the key entirely (not just setting to null). Parent keys are preserved.
listEnumerate work units by scanning .workflows/ for manifest.json files. Skips dot-prefixed directories (.archive, .state, .cache).
# All work units
node .claude/skills/workflow-manifest/scripts/manifest.cjs list
# Filter by status
node .claude/skills/workflow-manifest/scripts/manifest.cjs list --status in-progress
# Filter by work type
node .claude/skills/workflow-manifest/scripts/manifest.cjs list --work-type epic
# Combined filters
node .claude/skills/workflow-manifest/scripts/manifest.cjs list --status in-progress --work-type feature
Output: JSON array of manifest objects.
init-phaseRegister a topic within a phase. Creates phases.<phase>.items.<topic> with { "status": "in-progress" }. Requires a 3-segment path.
node .claude/skills/workflow-manifest/scripts/manifest.cjs init-phase <name>.discussion.<topic>
Errors if item/phase already exists.
pushAppend a value to an array field. Creates the array if it doesn't exist. Errors if the field exists but is not an array. Three levels:
Work-unit level (1 segment):
node .claude/skills/workflow-manifest/scripts/manifest.cjs push <name> tags "v1"
Phase level (2 segments):
node .claude/skills/workflow-manifest/scripts/manifest.cjs push <name>.research analysis_cache.files "a.md"
Topic level (3 segments):
node .claude/skills/workflow-manifest/scripts/manifest.cjs push <name>.implementation.{topic} completed_tasks "{topic}-1-1"
node .claude/skills/workflow-manifest/scripts/manifest.cjs push <name>.implementation.{topic} completed_phases 1
node .claude/skills/workflow-manifest/scripts/manifest.cjs push <name>.review.{topic} reviewed_tasks "{topic}-1-1"
pullRemove the first occurrence of a value from an array field. No-op if the field doesn't exist, is not an array, or doesn't contain the value. Three levels:
Work-unit level (1 segment):
node .claude/skills/workflow-manifest/scripts/manifest.cjs pull <name> tags "v1"
Phase level (2 segments):
node .claude/skills/workflow-manifest/scripts/manifest.cjs pull <name>.discovery dismissed "topic-name"
Topic level (3 segments):
node .claude/skills/workflow-manifest/scripts/manifest.cjs pull <name>.implementation.{topic} completed_tasks "{topic}-1-1"
key-ofFind the key in an object whose value matches. Useful for reverse lookups — e.g., finding an internal ID from an external ID in task_map.
# Find internal ID from external ID
node .claude/skills/workflow-manifest/scripts/manifest.cjs key-of <name>.planning.<topic> task_map {external_id}
Output: the matching key to stdout (e.g., portal-1-1). Errors if the value is not found or the path is not an object.
existsCheck whether a work unit, field, or phase entry exists. Always exits 0 — both true and false are valid results. Outputs true or false to stdout, nothing to stderr.
# Does the work unit exist?
node .claude/skills/workflow-manifest/scripts/manifest.cjs exists <name>
# Does a field path exist? (work-unit level)
node .claude/skills/workflow-manifest/scripts/manifest.cjs exists <name> work_type
# Does a phase-level field exist?
node .claude/skills/workflow-manifest/scripts/manifest.cjs exists <name>.discussion
# Does a topic entry exist?
node .claude/skills/workflow-manifest/scripts/manifest.cjs exists <name>.discussion.auth-flow
node .claude/skills/workflow-manifest/scripts/manifest.cjs exists <name>.discussion.auth-flow status
# Wildcard: does any topic in the phase have this field?
node .claude/skills/workflow-manifest/scripts/manifest.cjs exists '<name>.discussion.*'
node .claude/skills/workflow-manifest/scripts/manifest.cjs exists '<name>.discussion.*' status
If the work unit doesn't exist and a deeper path is requested, outputs false (no error). Actual usage errors (missing args, invalid phase name) still use die().
Wildcard topic (* as third segment) — outputs true if any topic in the phase matches (has the field, or exists at all if no field specified), false otherwise. Always exits 0.
project (legacy convenience)List or get work units from the project manifest. Prefer the project.* dot-path syntax via get/set/exists/delete/push for new usage. The project list --type filter is retained as a convenience.
List work units:
# All registered work units
node .claude/skills/workflow-manifest/scripts/manifest.cjs project list
# Filter by work type
node .claude/skills/workflow-manifest/scripts/manifest.cjs project list --type cross-cutting
Output: one name per line. No output if none found.
Get work unit entry:
node .claude/skills/workflow-manifest/scripts/manifest.cjs project get <name>
Output: work_type: <type>. Errors if not found.
The project manifest is automatically updated when init creates a new work unit.
Project-wide settings are stored in the defaults section of the project manifest. These serve as suggestions when starting new topics — the user always confirms or overrides. The chosen value is saved back as "most recently used".
| Default | Description | Used by |
|---------|-------------|---------|
| plan_format | Output format for plans (see workflow-planning-process/references/output-formats.md for available values) | Planning |
| project_skills | Array of skill paths used during implementation | Implementation |
| linters | Array of linter configs used during TDD cycle | Implementation |
The cascade is: project defaults (suggestion) → topic level (actual value used). There is no phase-level storage.
Most work-unit data lives under phases.{phase}.items.{topic}. A small number of fields sit at the work-unit root — identity (name, work_type, status, created, description), the phases map, and a top-level imports array.
imports[] — entries describing seed files copied into .workflows/{work_unit}/imports/ at work-unit creation. Each entry is a {path, imported_at} object. The CLI does not validate entry shape; the importing skill is responsible for forming well-shaped entries.
$MANIFEST push {work_unit} imports '{"path":"imports/seed.md","imported_at":"2026-05-09T10:00:00Z"}'
$MANIFEST get {work_unit} imports
$MANIFEST pull {work_unit} imports '{"path":"imports/seed.md","imported_at":"2026-05-09T10:00:00Z"}'
imports[] is the first top-level array field on work-unit manifests — every other array lives under phases. Convention for new top-level array fields: use push/pull for incremental updates (auto-creates the array on first push); use set for full replacement. pull matches values by deep equality, so the JSON passed to remove an entry must match the stored shape exactly.
The CLI validates structural values to prevent invalid state:
| Field | Valid Values |
|--------------------------------|----------------------------------------------------|
| work_type | epic, feature, bugfix, quick-fix, cross-cutting |
| status (work unit) | in-progress, completed, cancelled |
| Item status (discovery) | in-progress |
| Item status (research) | in-progress, completed |
| Item status (discussion) | in-progress, completed |
| Item status (investigation) | in-progress, completed |
| Item status (scoping) | in-progress, completed |
| Item status (specification) | in-progress, completed, superseded, promoted |
| Item status (planning) | in-progress, completed |
| Item status (implementation) | in-progress, completed |
| Item status (review) | in-progress, completed |
| Gate modes (*_gate_mode) | gated, auto |
Status is always tracked at the item level (phases.{phase}.items.{topic}.status), never at the phase level.
in-progress, completed).lock file next to manifest, exclusive create (wx flag), 30s stale detection. Prevents concurrent session conflicts..tmp then fs.renameSync. No partial writes.init creates the work unit directory. Phase directories are created by skills when they enter that phase, not by the CLI.items structure.tools
--- name: workflow-discovery user-invocable: false allowed-tools: Bash(node .claude/skills/workflow-discovery/scripts/discovery.cjs), Bash(node .claude/skills/workflow-manifest/scripts/manifest.cjs), Bash(node .claude/skills/workflow-knowledge/scripts/knowledge.cjs), Bash(git status), Bash(git add), Bash(git commit), Bash(cp), Bash(mkdir -p .workflows/), Bash(mv .workflows/.inbox/) --- # Discovery The universal first phase. Shape the work the user is bringing — confirm what kind of work it is,
tools
--- name: workflow-continue-quickfix user-invocable: false allowed-tools: Bash(node .claude/skills/workflow-continue-quickfix/scripts/discovery.cjs), Bash(node .claude/skills/workflow-manifest/scripts/manifest.cjs), Bash(node .claude/skills/workflow-knowledge/scripts/knowledge.cjs) --- Continue an in-progress quick-fix. Determines current phase and routes to the appropriate phase skill. > **⚠️ ZERO OUTPUT RULE**: Do not narrate your processing. Produce no output until a step or reference file
tools
--- name: workflow-continue-feature user-invocable: false allowed-tools: Bash(node .claude/skills/workflow-continue-feature/scripts/discovery.cjs), Bash(node .claude/skills/workflow-manifest/scripts/manifest.cjs), Bash(node .claude/skills/workflow-knowledge/scripts/knowledge.cjs) --- Continue an in-progress feature. Determines current phase and routes to the appropriate phase skill. > **⚠️ ZERO OUTPUT RULE**: Do not narrate your processing. Produce no output until a step or reference file expl
tools
--- name: workflow-continue-epic user-invocable: false allowed-tools: Bash(node .claude/skills/workflow-continue-epic/scripts/discovery.cjs), Bash(node .claude/skills/workflow-manifest/scripts/manifest.cjs), Bash(node .claude/skills/workflow-knowledge/scripts/knowledge.cjs), Bash(node .claude/skills/workflow-legacy-research-split/scripts/detect.cjs), Bash(node .claude/skills/workflow-discovery/scripts/discovery.cjs) --- Continue an in-progress epic. Shows full phase-by-phase state and routes to