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:
AIOSAI
2026-03-17 18:14:15 -07:00
co-authored by @flow
parent 202c2294a7
commit 3876ff04ed
7 changed files with 108 additions and 39 deletions
+59 -10
View File
@@ -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(
+20 -9
View File
@@ -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
+6 -4
View File
@@ -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
# =============================================
+4 -5
View File
@@ -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
# =============================================
+6 -5
View File
@@ -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
# =============================================
+6 -4
View File
@@ -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
# =============================================