manage-logging

Unified logging infrastructure for script execution, work progress, and decision tracking

SKILL.md

GitHub

Manage Logging Skill

Unified logging infrastructure providing script execution logging, semantic work progress tracking, and decision logging.

Scope: hybrid means this skill operates at both plan-scoped and global levels. When a plan_id is provided and the plan directory exists, logs go to .plan/local/plans/{plan-id}/logs/. Otherwise, logs fall back to global daily files at .plan/local/logs/.

A third, opt-in store exists for orchestrator epics: --store orchestrator on the work / decision / read verbs routes to the logs/ directory of the git-tracked orchestrator epic tree, logically .plan/orchestrator/{slug}/logs/. The tree is resolved through the orchestrator store seam (file_ops.get_store_dir): on the current checkout's git-tracked config tier with orchestrator.use_worktree off, and inside the shared ledger worktree with it on — see tools-file-ops/SKILL.md for the store root. The default store is plans.

The epic tree is git-tracked; its logs/ subtree is not. .gitignore un-ignores .plan/orchestrator/ and .plan/archived-orchestrators/ and then re-ignores */logs/ beneath both, because a per-verb append log is a pure audit artifact whose line-level churn would conflict on every run. So an orchestrator log entry stays machine-local even though the ledger it sits beside is versioned with the repository.

Enforcement

Base contract: See manage-contract.md for shared enforcement rules, TOON output format, and error response patterns.

Skill-specific constraints:

  • The work and decision write verbs are silent on success and report a write that did not land as status: error with exit 1 (see Write outcome); a workflow that records an outcome after writing a line checks that return. The script write verb is fire-and-forget
  • Only valid log levels: INFO, WARNING, ERROR
  • Log type determines the output file (script, work, decision)
  • Work log messages must include [CATEGORY] (caller) prefix format

Overview

This skill provides a single unified API for three logging concerns:

  1. Script Execution Logging: Tracking of script executor invocations (type: script)
  2. Work Logging: Semantic tracking of work progress (type: work)
  3. Decision Logging: Tracking of decisions made during execution (type: decision)

Log Files

All plan-scoped logs are stored in the logs/ subdirectory of the plan.

Script Execution Log

File: .plan/local/plans/{plan-id}/logs/script-execution.log (plan-scoped) Fallback: .plan/local/logs/script-execution-YYYY-MM-DD.log (global)

Work Log

File: .plan/local/plans/{plan-id}/logs/work.log Fallback: .plan/local/logs/work-YYYY-MM-DD.log (global)

Decision Log

File: .plan/local/plans/{plan-id}/logs/decision.log Fallback: .plan/local/logs/decision-YYYY-MM-DD.log (global)

Orchestrator Store (--store orchestrator)

Files: .plan/orchestrator/{slug}/logs/work.log and .plan/orchestrator/{slug}/logs/decision.log (git-ignored, inside the git-tracked epic tree — no global fallback; --plan-id carries the epic slug). The orchestrator logged-event vocabulary (decision, interaction, plan-status-change, reconciliation) is defined in standards/log-format.md § Orchestrator Logged Events.


CLI Script Usage

Script: plan-marshall:manage-logging:manage-logging

Write API

python3 .plan/execute-script.py plan-marshall:manage-logging:manage-logging \
  {type} [--plan-id {plan_id}] --level {level} --message "{message}"

Arguments:

Argument Required Values Description
type Yes script, work, decision Log type (determines output file)
--plan-id No kebab-case Plan identifier. Optional on write subcommands — when omitted, the entry is written to the dated global log under .plan/local/logs/ (the first-class global/no-plan path); when supplied and resolving to an initialized plan, the entry is plan-scoped. Required with --store orchestrator (the epic slug — the orchestrator store has no global fallback).
--level Yes INFO, WARNING, ERROR Log level
--message Yes string Log message
--store No plans (default), orchestrator Store selection — work and decision verbs only (script has no store flag). orchestrator writes to .plan/orchestrator/{slug}/logs/{work,decision}.log.

Output: None on success — the verb prints nothing and exits 0.

Write outcome

Verb Entry written Entry not written
work, decision Prints nothing, exit 0 Prints a status: error TOON with error: write_failed (plus log_type, plan_id and a message carrying the underlying error), exit 1
script Prints nothing, exit 0 Prints nothing, exit 0 — best-effort

"Not written" is any failure to resolve, create or append to the log file: an unwritable logs/ directory, a log path that is a directory, a full disk. These two verbs write through the checked path plan_logging.write_log_entry, which raises instead of swallowing, so the reported outcome applies to every work or decision call made through the executor. An in-process caller of log_entry is not covered: log_entry is best-effort (see Python Import).

The script verb keeps the best-effort contract because its line is the executor's own audit of a script call, not a record a workflow acts on: failing the verb would turn a lost audit line into a failed step. Three cases are not write failures: logging disabled (LOG_ENABLED = False) is a silent exit 0; an orchestrator-store refusal keeps its own payload (see Error Responses); --store orchestrator without --plan-id keeps missing_plan_id.

Global / no-plan logging path

Omitting --plan-id is a first-class call, not an error — the entry lands in the dated global log under .plan/local/logs/ (work-{date}.log, decision-{date}.log, or script-execution-{date}.log). This is the supported path for plan-less callers — components that run BEFORE any plan exists, such as marshall-steward (the project-configuration wizard), which write here rather than passing a plan id that does not exist.

STEWARD audit namespace

marshall-steward writes its audit trail through the global/no-plan path with a stable message prefix:

[STEWARD] (plan-marshall:marshall-steward) {what happened}

One entry per AskUserQuestion answer and per auto-decision (e.g. [STEWARD] (plan-marshall:marshall-steward) Selected balanced effort preset). Decision-class entries use the decision subcommand (the file is the category, so no [DECISION] prefix); informational/status entries use the work subcommand and carry a [STEWARD] category bracket. Example:

python3 .plan/execute-script.py plan-marshall:manage-logging:manage-logging \
  decision --level INFO --message "[STEWARD] (plan-marshall:marshall-steward) Selected balanced effort preset"

Separator API

python3 .plan/execute-script.py plan-marshall:manage-logging:manage-logging \
  separator --plan-id {plan_id} [--type {work|script|decision}]

Arguments:

Argument Required Default Description
--plan-id Yes - Plan identifier
--type No work Log type: work, script, or decision

Output: None (appends a blank line to the log file for visual separation between phases)

Read API

python3 .plan/execute-script.py plan-marshall:manage-logging:manage-logging \
  read --plan-id {plan_id} --type {work|script|decision} [--limit N] [--phase PHASE] [--store {plans|orchestrator}]

Arguments:

Argument Required Description
--plan-id Yes Plan identifier (or epic slug with --store orchestrator)
--type Yes Log type: work, script, or decision
--limit No Max entries to return (most recent)
--phase No Filter by phase (work/decision logs only)
--store No Store selection: plans (default) or orchestrator

Output (TOON):

status: success
plan_id: EXAMPLE-PLAN
log_type: work
total_entries: 5
showing: 3

entries:
  - timestamp: 2025-12-11T11:14:30Z
    level: INFO
    hash_id: c8d3e2
    message: [STATUS] (plan-marshall:phase-1-init) Starting init phase
    phase: 1-init
  - timestamp: 2025-12-11T11:15:20Z
    level: INFO
    hash_id: f1a9b3
    message: [ARTIFACT] (plan-marshall:phase-1-init) Created deliverable: auth module

Examples

# Write: Script execution logging
python3 .plan/execute-script.py plan-marshall:manage-logging:manage-logging \
  script --plan-id EXAMPLE-PLAN --level INFO --message "plan-marshall:manage-tasks:manage-tasks add (0.15s)"

python3 .plan/execute-script.py plan-marshall:manage-logging:manage-logging \
  script --plan-id EXAMPLE-PLAN --level ERROR --message "plan-marshall:manage-tasks:manage-tasks add failed (exit 1)"

# Write: Work logging (include [CATEGORY] (caller) prefix)
python3 .plan/execute-script.py plan-marshall:manage-logging:manage-logging \
  work --plan-id EXAMPLE-PLAN --level INFO --message "[ARTIFACT] (plan-marshall:phase-1-init) Created deliverable: auth module"

python3 .plan/execute-script.py plan-marshall:manage-logging:manage-logging \
  work --plan-id EXAMPLE-PLAN --level WARNING --message "[STATUS] (plan-marshall:phase-5-execute) Skipped validation step"

# Write: Decision logging (NO [DECISION] prefix - file is the category)
python3 .plan/execute-script.py plan-marshall:manage-logging:manage-logging \
  decision --plan-id EXAMPLE-PLAN --level INFO --message "(plan-marshall:phase-1-init) Detected domain: java - pom.xml found"

python3 .plan/execute-script.py plan-marshall:manage-logging:manage-logging \
  decision --plan-id EXAMPLE-PLAN --level INFO --message "(pm-plugin-development:ext-outline-workflow) Scope: bundles=all"

# Read: All work log entries
python3 .plan/execute-script.py plan-marshall:manage-logging:manage-logging \
  read --plan-id EXAMPLE-PLAN --type work

# Read: All decision log entries
python3 .plan/execute-script.py plan-marshall:manage-logging:manage-logging \
  read --plan-id EXAMPLE-PLAN --type decision

# Read: Last 5 work log entries
python3 .plan/execute-script.py plan-marshall:manage-logging:manage-logging \
  read --plan-id EXAMPLE-PLAN --type work --limit 5

# Read: Work log entries for 1-init phase only
python3 .plan/execute-script.py plan-marshall:manage-logging:manage-logging \
  read --plan-id EXAMPLE-PLAN --type work --phase 1-init

Canonical invocations

The canonical argparse surface for manage-logging.py. The plugin-doctor missing-canonical-block rule checks that this section is PRESENT, matching its heading only — the body is never read; manage-invocation-invalid derives its accept-set from a live --help walk rather than from this section. Consuming skills xref this section by name (e.g., "see manage-logging Canonical invocations → work") instead of restating the command inline.

--plan-id is OPTIONAL on the three write subcommands (work / decision / script); omitting it writes to the dated global log under .plan/local/logs/. --store is available on work, decision, and read only (default plans); --store orchestrator requires --plan-id (the epic slug) and routes to .plan/orchestrator/{slug}/logs/.

work

python3 .plan/execute-script.py plan-marshall:manage-logging:manage-logging work \
  [--plan-id PLAN_ID] --level {INFO|WARNING|ERROR} --message TEXT \
  [--store {plans|orchestrator}]

decision

python3 .plan/execute-script.py plan-marshall:manage-logging:manage-logging decision \
  [--plan-id PLAN_ID] --level {INFO|WARNING|ERROR} --message TEXT \
  [--store {plans|orchestrator}]

script

python3 .plan/execute-script.py plan-marshall:manage-logging:manage-logging script \
  [--plan-id PLAN_ID] --level {INFO|WARNING|ERROR} --message TEXT

separator

python3 .plan/execute-script.py plan-marshall:manage-logging:manage-logging separator \
  --plan-id PLAN_ID [--type {work|decision|script}]

Default --type is work.

read

python3 .plan/execute-script.py plan-marshall:manage-logging:manage-logging read \
  --plan-id PLAN_ID --type {work|decision|script} \
  [--limit N] [--phase PHASE] [--store {plans|orchestrator}]

--phase filters only work and decision log reads.


Log Format

Standard Entry Structure

[{timestamp}] [{level}] [{hash}] {message}

Every log entry automatically includes a 6-character hash computed from the message content. This provides:

  • Transparent ID generation: Callers don't need to compute or track hashes
  • Deterministic: Same message always produces the same hash
  • Traceability: Findings can be linked across stages (analysis → resolution → Q-gate)

Example Output

script-execution.log:

[2025-12-11T12:14:26Z] [INFO] [a3f2c1] plan-marshall:manage-files:manage-files create (0.19s)
[2025-12-11T12:17:50Z] [ERROR] [b7e4d9] plan-marshall:manage-tasks:manage-tasks add failed (exit 1)

work.log:

[2025-12-11T11:14:30Z] [INFO] [c8d3e2] [STATUS] (plan-marshall:phase-1-init) Starting init phase
[2025-12-11T11:15:20Z] [INFO] [f1a9b3] [ARTIFACT] (plan-marshall:phase-1-init) Created deliverable: auth module
[2025-12-11T11:17:30Z] [INFO] [e5c7d4] [PROGRESS] (plan-marshall:phase-5-execute) Task 1 completed

decision.log:

[2025-12-11T11:14:48Z] [INFO] [d2e8f1] (plan-marshall:phase-1-init) Detected domain: java - pom.xml found
[2025-12-11T11:20:15Z] [INFO] [a4b6c8] (pm-plugin-development:ext-outline-workflow) Scope: bundles=all

Note: Decision entries do NOT include a [DECISION] prefix - the file itself indicates the entry type. The hash ID (e.g., [d2e8f1]) is automatically generated from the message content.

Log Levels

Level Description
INFO Progress, informational, or successful completion message
WARNING Warning (non-fatal issue)
ERROR Error with details

Python Import (from scripts run via executor)

Scripts run via the executor have PYTHONPATH set up for cross-skill imports:

from plan_logging import log_entry

# Function signature:
# log_entry(log_type: str, plan_id: str | None, level: str, message: str) -> None
#   log_type: 'script', 'work', or 'decision'
#   plan_id:  plan identifier, or None for the global log
#   level:    'INFO', 'WARNING', or 'ERROR'
#   message:  log message text

# Log to global script log
log_entry('script', None, 'INFO', '[MY-COMPONENT] Processing started')

# Log to plan-specific log
log_entry('work', 'example-plan', 'INFO', '[ARTIFACT] Created deliverable')

Note: IDE warnings about unresolved imports are expected - PYTHONPATH is set at runtime by the executor.

Error behavior: log_entry is fire-and-forget. If the target directory doesn't exist or a write fails, the error is silently swallowed to avoid disrupting the calling script. This is intentional — logging as a side effect should never cause a script to fail. A caller that must know whether the entry landed calls write_log_entry (same signature) instead: it performs the append and raises on failure. It has two callers: the CLI work and decision verbs, which report the outcome, and log_entry, which delegates its append to it and swallows what it raises. The one exception to the swallow is a refusal from the orchestrator store seam on an orchestrator-store write (OrchestratorStoreUnavailable, see tools-file-ops/SKILL.md): it propagates rather than being swallowed, because swallowing it would report a log write that never landed.

plan_id validity is enforced below this API as well — see the invalid_plan_id note under Error Responses.


Storage Locations

Plan-Scoped Logs

.plan/local/plans/{plan-id}/
└── logs/
    ├── script-execution.log    # Script execution tracking
    ├── work.log                # Work progress tracking
    └── decision.log            # Decision tracking

Global Logs

.plan/local/logs/
├── script-execution-YYYY-MM-DD.log    # Daily global script logs
├── work-YYYY-MM-DD.log                # Daily global work logs (when no plan)
└── decision-YYYY-MM-DD.log            # Daily global decision logs

Orchestrator Logs

.plan/orchestrator/{slug}/      # git-tracked
└── logs/                       # git-ignored (re-ignored beneath the tracked tree)
    ├── work.log                # Interaction / plan-status-change / reconciliation events
    └── decision.log            # Orchestrator decision events

Scope Selection:

  • If --store orchestrator: orchestrator log in the epic tree the store seam resolves — the current checkout's tracked tier with orchestrator.use_worktree off, the shared ledger worktree with it on (requires plan_id = epic slug; no fallback)
  • If plan_id is provided and plan directory exists: plan-scoped log
  • Otherwise: global log (both script and work types supported)

Environment Variables

Variable Description Default
PLAN_BASE_DIR Overrides the runtime-state root (resolved by file_ops.get_base_dir) <plan-root>/.plan/local
LOG_MAX_OUTPUT Max chars to capture from stdout/stderr 2000
LOG_RETENTION_DAYS Days to keep global logs (used by cleanup_old_script_logs()) 7

Error Responses

See manage-contract.md for the standard error response format.

Error Code Cause
invalid_plan_id plan_id format invalid
missing_plan_id --store orchestrator supplied without --plan-id (the epic slug)
invalid_log_type Log type not in: script, work, decision
invalid_level Level not in: INFO, WARNING, ERROR
write_failed work / decision: the entry could not be written — the log path could not be resolved, created or appended to (exit 1)

Note: The Python log_entry() function is fire-and-forget and silently swallows errors to avoid disrupting callers. The CLI script (manage-logging) returns exit code 1 on validation errors and, for the work and decision verbs, on a failed write (write_failed); the script verb silently succeeds on I/O failures. A refusal from the orchestrator store seam on a --store orchestrator call is not an I/O failure and is not swallowed: it reaches the caller as status: error carrying the seam's code, with exit 0.

Note: invalid_plan_id is also enforced below the CLI boundary. A malformed plan_id handed to the Python API is rejected at get_log_path — the shared path resolver — so the entry is dropped instead of falling back to the global log; an absent (None) plan_id remains the first-class global path. The read verbs never reach that resolver with a malformed identifier: their own is_valid_plan_id pre-check surfaces it as the invalid_plan_id TOON error first. The CLI write verbs never reach it either: --plan-id is format-checked at the argument boundary.


Integration

Producers

Client Operation Purpose
Script executor log_script_execution() (auto) Log every script invocation with timing/exit code
Phase agents (1-6) work Log artifacts, progress, outcomes during execution
Phase agents (2-6) decision Log decision-making points during planning
manage-files log_entry() (import) Log file write operations

Consumers

Client Operation Purpose
phase-6-finalize read Query work/decision logs for PR summary
Debugging workflows read Inspect execution history

Scripts

Script Notation Description
manage-logging.py plan-marshall:manage-logging:manage-logging CLI for logging operations (write and read)
plan_logging.py - Python module (imported, not executed)

Related

  • manage-status — Phase tracking that logging augments
  • manage-metrics — Metric data that complements work logs
  • manage-files — Generic file operations used alongside logging