Task Visibility

Task visibility controls which tasks are exposed as entry points, which are accessible across package boundaries, and which are internal implementation details. The visibility system helps package authors create clean APIs while hiding internal complexity.

Visibility Scopes

DV Flow Manager supports three visibility scopes that can be assigned to tasks:

Visibility Scopes

Scope

Description

root

Task is an executable entry point. Shown in task listing when running dfm run without arguments.

export

Task is visible outside the package. Other packages can reference this task in their needs lists.

local

Task is only visible within its declaration fragment (e.g., within a task body or fragment file).

Tasks without any scope specifier have package visibility - they can be referenced by other tasks in the same package but are not shown as entry points and will generate a warning if referenced from other packages.

Specifying Scope

There are two ways to specify task visibility:

Using the scope Field

The scope field can be a single string or a list of scopes:

tasks:
# Single scope
- name: build
  scope: root
  run: make build

# Multiple scopes
- name: public_entry
  scope: [root, export]
  desc: "Public entry point visible to other packages"
  run: ./run.sh

Using Inline Scope Markers

For convenience, you can use root:, export:, or local: instead of name: to simultaneously name the task and set its scope:

tasks:
# Equivalent to name: build + scope: root
- root: build
  run: make build

# Equivalent to name: format + scope: export
- export: format
  run: format.sh

# Equivalent to name: helper + scope: local
- local: helper
  run: echo "internal"

Both inline markers and explicit scope: can be combined:

tasks:
# root inline marker + scope: export = both root and export
- root: main
  scope: export
  run: ./main.sh

Only one of name:, root:, export:, local:, or override: can be specified per task.

Root Scope

Tasks marked with scope: root are executable entry points. When you run dfm run without specifying a task, only root tasks are displayed:

package:
  name: my_project

  tasks:
  - root: build
    desc: "Build the project"
    run: make build

  - root: test
    desc: "Run tests"
    needs: [build]
    run: make test

  - name: internal_helper
    run: echo "helper"

Running dfm run without arguments:

$ dfm run
No task specified. Available Tasks:
my_project.build  - Build the project
my_project.test   - Run tests

Note that internal_helper is not shown because it lacks scope: root.

Note

Any task can still be run directly by name, regardless of visibility. Visibility only affects what is listed, not what is runnable.

Export Scope

Tasks marked with scope: export are visible outside the package. This is essential for creating reusable packages where other packages depend on your tasks.

# utils/flow.yaml
package:
  name: utils

  tasks:
  - export: format
    desc: "Format source files"
    run: format.sh

  - name: check_syntax
    run: check.sh
# main/flow.yaml
package:
  name: main
  imports:
  - utils/flow.yaml

  tasks:
  - name: build
    needs:
    - utils.format     # OK - format is export
    - utils.check_syntax  # WARNING - not marked export

When a task references a non-export task from another package, DV Flow Manager issues a warning:

Warning: Task 'main.build' references task 'utils.check_syntax'
in package 'utils' that is not marked 'export'

This warning helps identify unintended dependencies on internal implementation details of other packages.

Local Scope

Tasks marked with scope: local are only visible within their declaration context. This is useful for helper tasks in compound task bodies:

tasks:
- name: process_files
  body:
  - local: prepare
    run: ./prepare.sh

  - local: transform
    needs: [prepare]
    run: ./transform.sh

  - name: finalize
    needs: [transform]
    run: ./finalize.sh

Local tasks cannot be referenced from outside their containing body or fragment.

Default Package Visibility

Tasks without any scope specifier have package visibility:

  • Can be referenced by other tasks in the same package

  • Can be run directly by fully-qualified name

  • Are NOT shown in task listings (dfm run without arguments)

  • Generate a warning if referenced from other packages

package:
  name: my_pkg

  tasks:
  - name: internal_task
    run: echo "package-visible only"

  - root: public_entry
    needs: [internal_task]  # OK - same package
    run: echo "entry point"

Combining Scopes

Scopes can be combined for tasks that serve multiple purposes:

tasks:
# Entry point AND visible to other packages
- name: main
  scope: [root, export]
  run: ./main.sh

# Or using inline marker plus scope field
- root: main
  scope: export
  run: ./main.sh

Common combinations:

  • [root, export] - Public entry point (both CLI and API)

  • root alone - Entry point only for this package (not for dependents)

  • export alone - API for other packages, but not a primary entry point

Best Practices

Entry Point Selection

Mark tasks as root when they represent meaningful operations a user would run directly:

tasks:
# Good: Clear entry points
- root: build
  desc: "Build the project"
- root: test
  desc: "Run all tests"
- root: clean
  desc: "Clean build artifacts"

# Not root: Implementation details
- name: compile_rtl
- name: compile_tb
- name: link

API Design

For reusable packages, carefully consider which tasks to export:

package:
  name: my_library

  tasks:
  # Export: Stable API that users should depend on
  - export: compile
    desc: "Compile with library settings"

  # Not export: Internal implementation
  - name: gather_sources
  - name: process_includes

  # Export with root: Both API and entry point
  - root: test
    scope: export
    desc: "Run library self-tests"

Compound Task Organization

Use local scope for helper tasks that should not leak outside:

tasks:
- name: full_flow
  rundir: inherit
  body:
  # Local helpers - not visible outside
  - local: step1
    run: ./step1.sh

  - local: step2
    needs: [step1]
    run: ./step2.sh

  # Final step produces output
  - name: result
    needs: [step2]
    uses: std.FileSet
    with:
      include: "*.out"

Warning Resolution

If you see warnings about referencing non-export tasks:

  1. If intentional: Mark the referenced task as export

  2. If unintentional: Refactor to not depend on internal tasks

  3. If transitional: Add TODO comment and plan to fix

# Before: Warning about using internal task
- name: my_task
  needs: [other_pkg.internal_task]

# After: Either export the task or restructure
# Option 1: Ask other_pkg maintainer to export it
# Option 2: Restructure to use exported task
- name: my_task
  needs: [other_pkg.public_api]

Visibility in Fragments

Visibility works the same way in package fragments:

# src/rtl/flow.yaml
fragment:
  tasks:
  - root: compile_rtl
    desc: "Compile RTL"
    run: ./compile_rtl.sh

  - name: helper
    run: ./helper.sh

All visibility rules apply across the entire package, including all fragments.

Task Listing Behavior Summary

Scope

In Listing

Cross-Pkg

Direct Run

root

Yes

Warning

Yes

export

No

Yes

Yes

[root, export]

Yes

Yes

Yes

local

No

No

Limited

(none)

No

Warning

Yes

  • In Listing: Shown by dfm run without arguments

  • Cross-Pkg: Can be referenced in needs from another package

  • Direct Run: Can be executed by name