cuioss

manage-logging

Unified logging infrastructure for script execution, work progress, and decision tracking across the plan, global, and main-anchored orchestrator stores

cuioss 5 Updated 2mo ago

Resources

2
GitHub

Install

npx skillscat add cuioss/plan-marshall/manage-logging

Install via the SkillsCat registry.

SKILL.md

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/plans/{plan-id}/logs/. Otherwise, logs fall back to global daily files at .plan/logs/.

A third, opt-in store exists for orchestrator epics: --store orchestrator on the work / decision / read verbs routes to the main-anchored orchestrator tree at .plan/local/orchestrator/{slug}/logs/ (resolved via file_ops.get_store_dir, stable across sessions and worktree cwds). The default store is plans — every existing call path is unchanged.

Enforcement

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

Skill-specific constraints:

  • Log entries are fire-and-forget (no output parsing needed)
  • 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/plans/{plan-id}/logs/script-execution.log (plan-scoped)
Fallback: .plan/logs/script-execution-YYYY-MM-DD.log (global)

Work Log

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

Decision Log

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

Orchestrator Store (--store orchestrator)

Files: .plan/local/orchestrator/{slug}/logs/work.log and .plan/local/orchestrator/{slug}/logs/decision.log (main-anchored — 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/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 main-anchored to .plan/local/orchestrator/{slug}/logs/{work,decision}.log.

Output: None (exit code only)

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/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). It replaces the previous workaround of passing a non-existent plan id to force a global fallback.

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-task:manage-task 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-task:manage-task 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 D4 plugin-doctor analyzer
(_analyze_manage_invocation.py) reads this section as source-of-truth for markdown
notation occurrences across the marketplace. 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/logs/. --store is available on work, decision, and read only (default plans); --store orchestrator requires --plan-id (the epic slug) and routes to .plan/local/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-task:manage-task 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: Logging calls are 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 should never cause a script to fail.

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/plans/{plan-id}/
└── logs/
    ├── script-execution.log    # Script execution tracking
    ├── work.log                # Work progress tracking
    └── decision.log            # Decision tracking

Global Logs

.plan/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/local/orchestrator/{slug}/
└── logs/
    ├── work.log                # Interaction / plan-status-change / reconciliation events
    └── decision.log            # Orchestrator decision events

Scope Selection:

  • If --store orchestrator: main-anchored orchestrator log (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 Base directory for .plan structure .plan
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 File system permission denied or directory missing

Note: Write operations are fire-and-forget — the Python log_entry() function silently swallows errors to avoid disrupting callers. The CLI script (manage-logging) returns exit code 1 on validation errors but silently succeeds on I/O failures.

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 fire-and-forget write verbs swallow it per the note above.


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