fix(flow): list DPLAN prefix, double error messages, local prompt
- List display now shows correct prefix (DPLAN-XXXX not FPLAN-XXXX) by tagging plans with source registry prefix during merge - Modules return True after handling commands (even on failure) to prevent flow.py from printing spurious "Unknown command" - Configured flow local prompt with architecture overview Co-Authored-By: @flow <flow@aipass>
This commit is contained in:
@@ -1,14 +1,63 @@
|
||||
# FLOW Branch-Local Context
|
||||
<!-- Source: src/aipass/flow/.aipass/aipass_local_prompt.md -->
|
||||
# Flow -- Plan Lifecycle Management
|
||||
|
||||
> Auto-created by aipass init. Customize for your branch.
|
||||
Flow is AIPass's unified plan lifecycle system. It creates, tracks, closes, and archives numbered work plans across multiple plan types (FPLAN, DPLAN) via a data-driven plugin architecture.
|
||||
|
||||
## Status: NEEDS CONFIGURATION
|
||||
## Commands
|
||||
|
||||
This file is injected into every AI conversation when working from this branch directory. Configure it with:
|
||||
```bash
|
||||
drone @flow create . "Subject" # FPLAN (default)
|
||||
drone @flow create . "Subject" master # FPLAN master template
|
||||
drone @flow create . "Design topic" dplan # DPLAN
|
||||
drone @flow close FPLAN-0042 # Close specific plan
|
||||
drone @flow close --all # Close all open plans
|
||||
drone @flow list # List open plans (all types)
|
||||
drone @flow list all # List all plans
|
||||
drone @flow restore FPLAN-0042 # Reopen a closed plan
|
||||
```
|
||||
|
||||
- Who this branch is (role, purpose)
|
||||
- Key commands and workflows
|
||||
- Architecture overview
|
||||
- Critical files and operational rules
|
||||
- Integration points with other branches
|
||||
## Architecture
|
||||
|
||||
- `apps/flow.py` -- Entry point. Auto-discovers modules in `apps/modules/` via `handle_command()` convention.
|
||||
- `apps/modules/` -- Thin orchestrators. No business logic. Route to handlers and display results.
|
||||
- `apps/handlers/` -- Implementation. Grouped by domain: `plan/`, `registry/`, `template/`, `dashboard/`, `mbank/`, `summary/`.
|
||||
- `plan_types/` -- Data-only plugins. Each subdirectory has `plan_type.json` config + `templates/` with Markdown templates.
|
||||
- `flow_json/` -- Per-type JSON registries (`fplan_registry.json`, `dplan_registry.json`).
|
||||
|
||||
## Plan Type Plugins
|
||||
|
||||
Plan types are DATA, not code. Each plugin directory under `plan_types/` contains:
|
||||
- `plan_type.json` -- prefix, digits, registry_file, available_templates, default_template
|
||||
- `templates/` -- Markdown plan templates
|
||||
|
||||
Discovered at runtime by `apps/handlers/template/plan_type_loader.py`. Add a new type by creating a new directory with these files.
|
||||
|
||||
| Type | Prefix | Registry File | Templates |
|
||||
|------|--------|---------------|-----------|
|
||||
| flow_plans | FPLAN | fplan_registry.json | default, master |
|
||||
| dev_plans | DPLAN | dplan_registry.json | default |
|
||||
|
||||
## Critical Files
|
||||
|
||||
- `apps/flow.py` -- CLI entry point, module discovery, command routing
|
||||
- `apps/modules/create_plan.py` -- Plan creation orchestrator
|
||||
- `apps/modules/close_plan.py` -- Plan closure orchestrator (async post-processing, archival)
|
||||
- `apps/modules/list_plans.py` -- Multi-registry plan listing
|
||||
- `apps/handlers/plan/list_ops.py` -- Merges plans from all registries for display
|
||||
- `apps/handlers/plan/display.py` -- All formatting functions (prefix-aware)
|
||||
- `apps/handlers/plan/close_ops.py` -- Close implementation (file ops, registry update, vector intake)
|
||||
- `apps/handlers/template/plan_type_loader.py` -- Plugin discovery and config resolution
|
||||
- `apps/handlers/registry/load_registry.py` -- Registry loader (supports per-type registry files)
|
||||
|
||||
## Integration Points
|
||||
|
||||
- **aipass.cli** -- Rich console output (`console`, `header`, `success`, `error`, `warning`)
|
||||
- **aipass.prax** -- System logger
|
||||
- **aipass.memory** -- Vector intake pipeline on plan close
|
||||
- **aipass.trigger** -- Startup events and branch dashboard updates
|
||||
|
||||
## Conventions
|
||||
|
||||
- Modules return `True` from `handle_command()` when the command was recognized (even on failure), `False` only for "not my command".
|
||||
- Plan IDs follow `{PREFIX}-{NNNN}_topic_slug_YYYY-MM-DD.md`.
|
||||
- All file I/O uses `pathlib.Path` and `encoding='utf-8'`.
|
||||
- Handlers are stateless functions; modules inject dependencies.
|
||||
|
||||
@@ -300,13 +300,18 @@ def format_plan_info(plan_key: str, plan_info: Dict[str, Any], prefix: str = "FP
|
||||
Args:
|
||||
plan_key: Plan number (e.g., "0001")
|
||||
plan_info: Plan metadata dictionary
|
||||
prefix: Plan prefix (e.g. "FPLAN", "DPLAN")
|
||||
prefix: Plan prefix (e.g. "FPLAN", "DPLAN").
|
||||
Overridden by ``_source_prefix`` in *plan_info* when present
|
||||
(set by list_ops when merging multi-registry plans).
|
||||
|
||||
Returns:
|
||||
Formatted string with plan details
|
||||
"""
|
||||
from datetime import datetime
|
||||
|
||||
# Use source prefix if tagged by list_ops, otherwise fall back to arg
|
||||
display_prefix = plan_info.get("_source_prefix", prefix)
|
||||
|
||||
subject = plan_info.get("subject", "No subject")
|
||||
location = plan_info.get("relative_path", "unknown")
|
||||
status = plan_info.get("status", "unknown")
|
||||
@@ -320,7 +325,7 @@ def format_plan_info(plan_key: str, plan_info: Dict[str, Any], prefix: str = "FP
|
||||
except (ValueError, AttributeError):
|
||||
pass # Keep original value if parsing fails
|
||||
|
||||
return f" {prefix}-{plan_key} [{status:>6}] {location:<30} {subject:<40} {created}"
|
||||
return f" {display_prefix}-{plan_key} [{status:>6}] {location:<30} {subject:<40} {created}"
|
||||
|
||||
|
||||
def format_plans_list(
|
||||
|
||||
@@ -16,7 +16,7 @@ Usage:
|
||||
from aipass.flow.apps.handlers.plan.list_ops import list_plans_impl
|
||||
"""
|
||||
|
||||
from typing import Dict, Any
|
||||
from typing import Dict, Any, Tuple
|
||||
|
||||
from aipass.prax import logger
|
||||
|
||||
@@ -27,20 +27,29 @@ from aipass.prax import logger
|
||||
MODULE_NAME = "list_plans"
|
||||
|
||||
|
||||
def _get_all_registry_files() -> list[str]:
|
||||
"""Return per-type registry filenames via plan-type discovery."""
|
||||
def _get_all_registry_info() -> Tuple[list[str], Dict[str, str]]:
|
||||
"""Return per-type registry filenames and a registry_file -> prefix map.
|
||||
|
||||
Returns:
|
||||
(registry_files, prefix_map) where prefix_map maps e.g.
|
||||
"dplan_registry.json" -> "DPLAN". Empty list means caller
|
||||
should fall back to the default registry.
|
||||
"""
|
||||
try:
|
||||
from aipass.flow.apps.handlers.template.plan_type_loader import discover_plan_types # type: ignore[import-not-found]
|
||||
files: list[str] = []
|
||||
prefix_map: Dict[str, str] = {}
|
||||
for _key, config in discover_plan_types().items():
|
||||
rf = config.get("registry_file")
|
||||
if rf and rf not in files:
|
||||
files.append(rf)
|
||||
if rf:
|
||||
prefix_map[rf] = config.get("prefix", "FPLAN")
|
||||
if rf not in files:
|
||||
files.append(rf)
|
||||
if files:
|
||||
return files
|
||||
return files, prefix_map
|
||||
except Exception:
|
||||
pass
|
||||
return [] # empty means caller should fall back to default
|
||||
return [], {} # empty means caller should fall back to default
|
||||
|
||||
|
||||
# =============================================
|
||||
@@ -72,14 +81,16 @@ def list_plans_impl(
|
||||
try:
|
||||
# STEP 1: Load ALL per-type registries and merge plans
|
||||
merged_plans: Dict[str, Any] = {}
|
||||
reg_files = _get_all_registry_files()
|
||||
reg_files, reg_prefix_map = _get_all_registry_info()
|
||||
|
||||
if reg_files:
|
||||
for reg_file in reg_files:
|
||||
try:
|
||||
registry = load_registry(registry_file=reg_file)
|
||||
source_prefix = reg_prefix_map.get(reg_file, "FPLAN")
|
||||
for plan_num, plan_info in registry.get("plans", {}).items():
|
||||
# Prefix the key to avoid collisions across registries
|
||||
# Tag each plan with its source prefix for display
|
||||
plan_info["_source_prefix"] = source_prefix
|
||||
merged_plans[plan_num] = plan_info
|
||||
except Exception:
|
||||
continue
|
||||
|
||||
@@ -290,13 +290,15 @@ def handle_command(command: str, args: List[str]) -> bool:
|
||||
# 2. VALIDATE: Check for parsing errors
|
||||
if error:
|
||||
console.print(format_delete_usage_error())
|
||||
return False
|
||||
return True # Command was handled (error already displayed)
|
||||
|
||||
# 3. EXECUTE: Run workflow orchestrator
|
||||
success = close_plan(plan_num=plan_num, confirm=confirm, all_plans=all_plans)
|
||||
close_plan(plan_num=plan_num, confirm=confirm, all_plans=all_plans)
|
||||
|
||||
# 4. RETURN: Result (close_plan already handles all output)
|
||||
return success
|
||||
# 4. RETURN: True = command was handled (even if the operation failed,
|
||||
# the error has already been displayed -- returning False would cause
|
||||
# flow.py to print a spurious "Unknown command" message)
|
||||
return True
|
||||
|
||||
|
||||
# =============================================
|
||||
|
||||
@@ -275,11 +275,10 @@ def handle_command(command: str, args: List[str]) -> bool:
|
||||
)
|
||||
console.print(result_msg)
|
||||
|
||||
# Return boolean result
|
||||
if success:
|
||||
return True
|
||||
else:
|
||||
return False
|
||||
# Command was handled (even if the operation failed, the error has
|
||||
# already been displayed -- returning False would cause flow.py to
|
||||
# print a spurious "Unknown command" message)
|
||||
return True
|
||||
|
||||
|
||||
# =============================================
|
||||
|
||||
@@ -30,7 +30,7 @@ Filters:
|
||||
|
||||
import sys
|
||||
from pathlib import Path
|
||||
from typing import List, Dict, Any
|
||||
from typing import List
|
||||
|
||||
# INFRASTRUCTURE IMPORT PATTERN
|
||||
_PKG_ROOT = Path(__file__).resolve().parents[3] # file.py -> modules/ -> apps/ -> flow/ -> aipass/
|
||||
@@ -51,7 +51,6 @@ from aipass.flow.apps.handlers.registry.statistics import get_registry_statistic
|
||||
|
||||
# Plan display handler
|
||||
from aipass.flow.apps.handlers.plan.display import (
|
||||
format_plan_info,
|
||||
format_plans_list,
|
||||
format_statistics_summary
|
||||
)
|
||||
@@ -208,10 +207,12 @@ def handle_command(command: str, args: List[str]) -> bool:
|
||||
console.print("[dim]Valid filters: open, closed, all[/dim]")
|
||||
|
||||
# STEP 2: Execute workflow
|
||||
success = list_plans(filter_type)
|
||||
list_plans(filter_type)
|
||||
|
||||
# STEP 3: Return result
|
||||
return success
|
||||
# STEP 3: Command was handled (even if the operation failed, the error
|
||||
# has already been displayed -- returning False would cause flow.py to
|
||||
# print a spurious "Unknown command" message)
|
||||
return True
|
||||
|
||||
|
||||
# =============================================
|
||||
|
||||
@@ -226,13 +226,15 @@ def handle_command(command: str, args: List[str]) -> bool:
|
||||
# 2. VALIDATE: Check for parsing errors
|
||||
if error:
|
||||
console.print(format_restore_usage_error())
|
||||
return False
|
||||
return True # Command was handled (error already displayed)
|
||||
|
||||
# 3. EXECUTE: Run workflow orchestrator
|
||||
success = restore_plan(plan_num=plan_num)
|
||||
restore_plan(plan_num=plan_num)
|
||||
|
||||
# 4. RETURN: Result (restore_plan already handles all output)
|
||||
return success
|
||||
# 4. RETURN: True = command was handled (even if the operation failed,
|
||||
# the error has already been displayed -- returning False would cause
|
||||
# flow.py to print a spurious "Unknown command" message)
|
||||
return True
|
||||
|
||||
|
||||
# =============================================
|
||||
|
||||
Reference in New Issue
Block a user