use anyhow::{Context, Result};
use clap::{CommandFactory, Parser, Subcommand};
use clap_complete::{generate, Shell};
use colored::Colorize;
use log;
use wsb::st8::{St8Config, VersionInfo, detect_project_files, update_version_file, TemplateManager, WstemplateEngine};
use wsb::workspace_state::{WorkspaceState, WstemplateEntry};
use wsb::entities::EntityManager;
use wsb::logging::{self, log_operation_start, log_operation_complete, log_operation_error, log_warning, log_version_info};
use sqlx::SqlitePool;
use sqlx::Row;
use std::collections::HashMap;
use std::env;
use std::fs;
use std::io::Write;
use std::path::{Path, PathBuf};
use std::process::{self, Command};
use std::time::Instant;
#[derive(Parser, Debug)]
#[command(name = "wsb")]
#[command(version = env!("CARGO_PKG_VERSION"))]
#[command(about = "Workspace - All-in-one development tool suite")]
#[command(after_help = "Shell completions are automatically set up on first run.")]
struct Args {
#[command(subcommand)]
command: Commands,
}
#[derive(Subcommand, Debug)]
enum Commands {
/// Refactor files and directories using patterns
Refactor {
/// Arguments for refactor tool
#[command(flatten)]
args: wsb::refac::Args,
},
/// Git integration and version management
Git {
#[command(subcommand)]
command: Option<GitCommands>,
},
/// Template management
Template {
#[command(subcommand)]
action: TemplateAction,
},
/// Update version and render templates
Update {
/// Skip git integration (don't auto-add files)
#[arg(long)]
no_git: bool,
/// Automatically add updated files to git staging area
#[arg(long)]
git_add: bool,
/// Tag the current commit with the generated version suffixed by this tag
#[arg(long)]
tag: Option<String>,
},
/// Local trash can using a .scrap folder for files you want to delete
Scrap {
/// Paths to files or directories to move to .scrap folder
paths: Vec<std::path::PathBuf>,
#[command(subcommand)]
command: Option<ScrapCommands>,
},
/// Restore files from .scrap folder to their original locations
Unscrap {
/// Name of file/directory in .scrap to restore
name: Option<String>,
/// Force restore even if destination exists
#[arg(short, long)]
force: bool,
/// Restore to a different location
#[arg(short = 't', long)]
to: Option<std::path::PathBuf>,
},
/// Process input lines, replacing repeated tokens with a substitute character
Ldiff {
/// Character to use for substitution (default: โ)
#[arg(default_value = "โ")]
substitute_char: String,
},
/// AST-based code analysis and transformation
Code {
#[command(subcommand)]
action: Option<CodeAction>,
},
/// Intelligent test runner based on project type
Test {
/// Show what test command would be run without executing
#[arg(long)]
dry_run: bool,
/// Install missing test runners if needed
#[arg(long)]
install: bool,
/// Additional arguments to pass to test command
#[arg(last = true)]
args: Vec<String>,
},
/// MCP server for Claude integration with automatic session management
McpServer {
/// Port for HTTP server (default: 3000)
#[arg(short, long, default_value = "3000")]
port: u16,
/// Enable debug logging
#[arg(long)]
debug: bool,
/// Migrate features from features.md to database
#[arg(long)]
migrate: bool,
},
/// Create sample project with test data for dashboard testing
Sample {
/// Create sample project structure
#[arg(long)]
project: bool,
/// Populate database with test data
#[arg(long)]
data: bool,
/// Force overwrite existing data
#[arg(long)]
force: bool,
/// Output directory for sample project (default: ./sample-project)
#[arg(short, long, default_value = "sample-project")]
output: String,
},
/// Start development session with project context loading and validation
Start {
/// Continue from specific task ID
#[arg(long)]
continue_from: Option<String>,
/// Enable detailed diagnostic output during initialization
#[arg(long)]
debug_mode: bool,
/// Initialize new project with persistent knowledge management methodology
#[arg(long)]
project_setup: bool,
/// What to work on first in this session (does not limit session scope)
first_task: Option<String>,
},
/// End development session with documentation consolidation and feature updates
End {
/// Session summary description
#[arg(long)]
summary: Option<String>,
/// Enable detailed diagnostic output during session end
#[arg(long)]
debug_mode: bool,
/// Force session end even if validation fails
#[arg(long)]
force: bool,
/// Skip documentation updates
#[arg(long)]
skip_docs: bool,
},
/// Session artifact management - organize and track session-generated content
Artifacts {
#[command(subcommand)]
action: ArtifactAction,
},
/// Consolidate documentation with diagram management and information preservation
Consolidate {
/// Enable detailed diagnostic output during consolidation
#[arg(long)]
debug_mode: bool,
/// Force consolidation even if validation fails
#[arg(long)]
force: bool,
/// Generate architectural diagrams in DOT format
#[arg(long)]
generate_diagrams: bool,
/// Preserve complexity information during consolidation
#[arg(long)]
preserve_complexity: bool,
},
/// Display comprehensive project status with feature metrics and progress tracking
Status {
/// Enable detailed diagnostic output during status reporting
#[arg(long)]
debug_mode: bool,
/// Include feature breakdown by category
#[arg(long)]
include_features: bool,
/// Include detailed metrics and analytics
#[arg(long)]
include_metrics: bool,
/// Output format (human, json, summary)
#[arg(long, default_value = "human")]
format: String,
},
/// Feature-centric task management with automatic feature detection and linking
Task {
#[command(subcommand)]
action: TaskAction,
},
/// Project directive and rule management system for development methodology enforcement
Directive {
#[command(subcommand)]
action: DirectiveAction,
},
/// Feature management with state machine workflow and validation
Feature {
#[command(subcommand)]
action: FeatureAction,
},
/// Entity relationship management for complex entity links and dependencies
Relationship {
#[command(subcommand)]
action: RelationshipAction,
},
/// Note management for attaching notes to any entity or project-wide
Note {
#[command(subcommand)]
action: NoteAction,
},
/// Database backup, recovery, and maintenance operations
Database {
#[command(subcommand)]
action: DatabaseAction,
},
/// Session continuity and context management operations
Continuity {
#[command(subcommand)]
action: ContinuityAction,
},
/// Version management with database-driven major version and git-calculated components
Version {
#[command(subcommand)]
action: VersionAction,
},
/// Manage .wstemplate file rendering for version-stamping project files
Wstemplate {
#[command(subcommand)]
action: WstemplateAction,
},
}
#[derive(Subcommand, Debug)]
enum GitCommands {
/// Install version management as a pre-commit hook in the current git repository
Install {
/// Force reinstallation even if already installed
#[arg(short, long)]
force: bool,
},
/// Uninstall version management from the current git repository
Uninstall,
/// Show current version information
Show,
/// Show git integration status
Status,
/// Generate per-file diffs into a directory
Diff {
/// Base ref to diff against (commit, branch, tag). Without this, diffs unstaged changes.
#[arg(long)]
base: Option<String>,
/// Output directory for diff files
#[arg(short, long, default_value = "diffs")]
output: String,
/// Include staged changes (only when no base ref is given)
#[arg(short, long)]
staged: bool,
},
/// Manage git submodules as a synchronized fleet
Fleet {
#[command(subcommand)]
command: wsb::fleet::cli::Commands,
/// Enable verbose output
#[arg(short, long, global = true)]
verbose: bool,
/// Dry run mode (show what would happen without executing)
#[arg(short = 'n', long, global = true)]
dry_run: bool,
},
}
#[derive(Subcommand, Debug)]
enum TaskAction {
/// Add a new task with automatic feature detection
Add {
/// Task title
title: String,
/// Task description
description: String,
/// Link to specific feature (optional, auto-detected if not provided)
#[arg(short, long)]
feature: Option<String>,
/// Task priority (high, medium, low)
#[arg(short, long, default_value = "medium")]
priority: String,
/// Auto-detect and create feature if mentioned in description
#[arg(long)]
auto_feature: bool,
},
/// List tasks with filtering options
List {
/// Filter by task status (pending, in_progress, completed, blocked)
#[arg(short, long)]
status: Option<String>,
/// Filter by feature code (e.g., F0103)
#[arg(short, long)]
feature: Option<String>,
/// Filter by priority (high, medium, low)
#[arg(short, long)]
priority: Option<String>,
/// Show only recent tasks (last N days)
#[arg(short, long)]
recent: Option<u32>,
},
/// Show detailed task information
Show {
/// Task ID or title pattern to match
identifier: String,
},
/// Update task status or properties
Update {
/// Task ID to update
task_id: String,
/// New task status
#[arg(short, long)]
status: Option<String>,
/// Update task priority
#[arg(short, long)]
priority: Option<String>,
/// Add progress notes
#[arg(short, long)]
notes: Option<String>,
/// Link to feature (for feature association)
#[arg(short, long)]
feature: Option<String>,
},
/// Complete a task and update linked feature status
Complete {
/// Task ID to complete
task_id: String,
/// Completion notes or evidence
#[arg(short, long)]
notes: Option<String>,
/// Auto-advance linked feature state
#[arg(long)]
advance_feature: bool,
},
/// Block a task with reason and dependencies
Block {
/// Task ID to block
task_id: String,
/// Reason for blocking
reason: String,
/// Dependencies that must be resolved
#[arg(short, long)]
_dependencies: Vec<String>,
},
}
#[derive(Subcommand, Debug)]
enum DirectiveAction {
/// Add a new development directive or rule
Add {
/// Directive title
title: String,
/// Directive description or rule content
description: String,
/// Directive category (security, testing, coding, methodology, deployment)
#[arg(short, long, default_value = "methodology")]
category: String,
/// Enforcement level (mandatory, recommended, optional)
#[arg(short, long, default_value = "recommended")]
enforcement: String,
/// Priority level (critical, high, medium, low)
#[arg(short, long, default_value = "medium")]
priority: String,
},
/// List all directives with filtering options
List {
/// Filter by directive category
#[arg(short, long)]
category: Option<String>,
/// Filter by enforcement level
#[arg(short, long)]
enforcement: Option<String>,
/// Filter by priority level
#[arg(short, long)]
priority: Option<String>,
/// Show only recently added directives (last N days)
#[arg(short, long)]
recent: Option<u32>,
},
/// Show detailed directive information
Show {
/// Directive ID or title pattern to match
identifier: String,
},
/// Update directive properties or enforcement level
Update {
/// Directive ID to update
directive_id: String,
/// Update enforcement level
#[arg(short, long)]
enforcement: Option<String>,
/// Update priority level
#[arg(short, long)]
priority: Option<String>,
/// Update description
#[arg(short, long)]
description: Option<String>,
/// Update category
#[arg(short, long)]
category: Option<String>,
},
/// Remove a directive
Remove {
/// Directive ID to remove
directive_id: String,
/// Skip confirmation prompt
#[arg(short, long)]
force: bool,
},
/// Validate current project against all mandatory directives
Validate {
/// Category to validate (validates all if not specified)
#[arg(short, long)]
category: Option<String>,
/// Show detailed validation results
#[arg(short, long)]
verbose: bool,
/// Fail fast on first violation
#[arg(short, long)]
fail_fast: bool,
},
/// Check specific files or directories against directives
Check {
/// Files or directories to check
paths: Vec<std::path::PathBuf>,
/// Category of directives to check against
#[arg(short, long)]
category: Option<String>,
/// Output format (human, json, report)
#[arg(short, long, default_value = "human")]
format: String,
},
}
#[derive(Subcommand, Debug)]
enum FeatureAction {
/// Add a new feature to features.md
Add {
/// Feature title
title: String,
/// Feature description
description: String,
/// Feature category (core, command, mcp, etc.)
#[arg(short, long, default_value = "core")]
category: String,
/// Initial state (not_started, implemented, testing, completed)
#[arg(short, long, default_value = "not_started")]
state: String,
},
/// List features with filtering
List {
/// Filter by state (๐ข, ๐ , ๐ก, โ, โ ๏ธ, ๐ด)
#[arg(short, long)]
state: Option<String>,
/// Filter by category
#[arg(short, long)]
category: Option<String>,
/// Show recently modified features
#[arg(short, long)]
recent: Option<u32>,
},
/// Show detailed feature information
Show {
/// Feature ID (F0001, F0002, etc.)
feature_id: String,
},
/// Update feature state with validation
Update {
/// Feature ID to update
feature_id: String,
/// New state (implemented, testing, completed, issue, critical)
#[arg(short, long)]
state: Option<String>,
/// Evidence or notes for state change
#[arg(short, long)]
evidence: Option<String>,
/// Force state change without validation
#[arg(short, long)]
force: bool,
},
/// Validate feature state transitions
Validate {
/// Feature ID to validate (optional, validates all if not provided)
feature_id: Option<String>,
/// Verbose validation output
#[arg(short, long)]
verbose: bool,
},
/// Test automatic feature detection (F0107 demonstration)
DetectFeatures {
/// Test input text to analyze for potential features
input: String,
},
/// Monitor context usage and trigger session management
MonitorContext {
/// Current context usage percentage (0-100)
usage_percent: f64,
/// Total context tokens available
total_tokens: Option<u32>,
/// Used context tokens
used_tokens: Option<u32>,
},
/// Real-time feature management API for Claude integration
ApiCall {
/// API operation (add, update, list, validate)
operation: String,
/// Feature ID (for update/get operations)
feature_id: Option<String>,
/// JSON payload for the operation
payload: Option<String>,
},
}
#[derive(Subcommand, Debug)]
enum RelationshipAction {
/// Link entities with specific relationship types
Link {
/// Source entity ID
from_entity: String,
/// Source entity type (feature, task, session, project)
#[arg(short, long)]
from_type: String,
/// Target entity ID
to_entity: String,
/// Target entity type (feature, task, session, project)
#[arg(short, long)]
to_type: String,
/// Relationship type (implements, blocks, worked_in, depends_on)
#[arg(short, long, default_value = "depends_on")]
relationship_type: String,
/// Optional description of the relationship
#[arg(short, long)]
description: Option<String>,
},
/// List relationships for an entity
List {
/// Entity ID to show relationships for
entity_id: String,
/// Entity type (feature, task, session, project)
#[arg(short, long)]
entity_type: String,
/// Show only specific relationship types
#[arg(short, long)]
relationship_type: Option<String>,
/// Include resolved relationships
#[arg(long)]
include_resolved: bool,
},
/// Remove a relationship link
Unlink {
/// Dependency ID to remove
dependency_id: String,
/// Skip confirmation prompt
#[arg(short, long)]
force: bool,
},
/// Resolve a blocking relationship
Resolve {
/// Dependency ID to resolve
dependency_id: String,
/// Resolution description
#[arg(short, long)]
description: Option<String>,
},
/// Show relationship statistics for project
Stats {
/// Include detailed breakdown by type
#[arg(short, long)]
detailed: bool,
/// Output format (human, json)
#[arg(short, long, default_value = "human")]
format: String,
},
}
#[derive(Subcommand, Debug)]
enum NoteAction {
/// Add a note to a specific entity
Add {
/// Entity type (feature, task, session, project, etc.)
#[arg(short, long)]
entity_type: String,
/// Entity ID to attach note to
#[arg(short = 'i', long)]
entity_id: String,
/// Note title
title: String,
/// Note content
content: String,
/// Note type (architecture, decision, reminder, observation, reference, evidence, progress, issue)
#[arg(short = 't', long, default_value = "observation")]
note_type: String,
/// Optional tags for the note
#[arg(long)]
tags: Option<String>,
},
/// Add a project-wide note
AddProject {
/// Note title
title: String,
/// Note content
content: String,
/// Note type (architecture, decision, reminder, observation, reference, evidence, progress, issue)
#[arg(short = 't', long, default_value = "architecture")]
note_type: String,
/// Optional tags for the note
#[arg(long)]
tags: Option<String>,
},
/// List notes with optional filtering
List {
/// Filter by entity type
#[arg(short, long)]
entity_type: Option<String>,
/// Filter by entity ID
#[arg(short = 'i', long)]
entity_id: Option<String>,
/// Filter by note type
#[arg(short = 't', long)]
note_type: Option<String>,
/// Show only project-wide notes
#[arg(long)]
project_wide: bool,
/// Show only pinned notes
#[arg(long)]
pinned: bool,
},
/// Search notes by content or category
Search {
/// Search query to match in title or content
query: String,
/// Filter by note type
#[arg(short = 't', long)]
note_type: Option<String>,
/// Output format (human, json)
#[arg(short, long, default_value = "human")]
format: String,
},
/// Update an existing note
Update {
/// Note ID to update
note_id: String,
/// New title (optional)
#[arg(long)]
title: Option<String>,
/// New content (optional)
#[arg(long)]
content: Option<String>,
/// New tags (optional)
#[arg(long)]
tags: Option<String>,
},
/// Delete a note
Delete {
/// Note ID to delete
note_id: String,
/// Skip confirmation prompt
#[arg(short, long)]
force: bool,
},
/// Pin or unpin a note for importance
Pin {
/// Note ID to toggle pin status
note_id: String,
},
/// Link a note to another entity or note
Link {
/// Source note ID
source_note_id: String,
/// Target entity or note ID
target_id: String,
/// Target type (note, entity)
#[arg(short = 't', long, default_value = "entity")]
target_type: String,
/// Entity type if target is entity (feature, task, project, etc.)
#[arg(long)]
entity_type: Option<String>,
/// Link type (reference, response_to, related, blocks, depends_on)
#[arg(short = 'l', long, default_value = "reference")]
link_type: String,
},
/// Remove a link between notes/entities
Unlink {
/// Link ID to remove
link_id: String,
/// Skip confirmation prompt
#[arg(short, long)]
force: bool,
},
/// List links for a note or entity
ListLinks {
/// Entity or note ID to show links for
id: String,
/// Show incoming links (pointing to this entity)
#[arg(long)]
incoming: bool,
/// Show outgoing links (from this entity)
#[arg(long)]
outgoing: bool,
/// Output format (human, json)
#[arg(short, long, default_value = "human")]
format: String,
},
}
#[derive(Subcommand, Debug)]
enum TemplateAction {
/// Add a new template
Add {
/// Template name
name: String,
/// Template content (file path or inline content)
template: String,
/// Output path (where template will be rendered)
#[arg(short, long)]
output: Option<String>,
/// Template description
#[arg(short, long)]
description: Option<String>,
},
/// List all templates
List,
/// Show template details
Show {
/// Template name
name: String,
},
/// Enable or disable a template
Enable {
/// Template name
name: String,
/// Disable the template instead of enabling it
#[arg(long)]
disable: bool,
},
/// Remove a template
Remove {
/// Template name
name: String,
},
/// Render all enabled templates
Render,
/// Generate documentation from database entities
GenerateDocs {
/// Documentation type to generate (claude, features, progress, status, all)
#[arg(default_value = "all")]
doc_type: String,
/// Output directory for generated files
#[arg(short, long)]
output: Option<String>,
/// Force overwrite existing files
#[arg(short, long)]
force: bool,
},
/// Initialize predefined documentation templates
InitDocs {
/// Force reinitialize even if templates exist
#[arg(short, long)]
force: bool,
},
}
#[derive(Subcommand, Debug)]
enum DatabaseAction {
/// Create database backup with metadata
Backup {
/// Backup directory (default: .wsb/backups)
#[arg(short, long)]
backup_dir: Option<String>,
/// Enable compression
#[arg(short, long)]
compress: bool,
/// Maximum number of backups to keep
#[arg(short, long, default_value = "10")]
max_backups: usize,
},
/// List available database backups
List {
/// Backup directory to search
#[arg(short, long)]
backup_dir: Option<String>,
/// Output format (table, json)
#[arg(short, long, default_value = "table")]
format: String,
},
/// Restore database from backup
Restore {
/// Backup ID or path to restore from
backup_id: String,
/// Target database path (default: current project database)
#[arg(short, long)]
target: Option<String>,
/// Force restore without confirmation
#[arg(short, long)]
force: bool,
},
/// Clean up old backups beyond retention limit
Cleanup {
/// Backup directory
#[arg(short, long)]
backup_dir: Option<String>,
/// Maximum backups to retain
#[arg(short, long, default_value = "10")]
max_backups: usize,
/// Show what would be removed without actually removing
#[arg(short = 'n', long)]
dry_run: bool,
},
/// Check database health and integrity
Health {
/// Include performance analysis
#[arg(short, long)]
performance: bool,
},
}
#[derive(Subcommand, Debug)]
enum CodeAction {
/// Show visual tree of current codebase structure
Tree {
/// Maximum depth to display (default: 3)
#[arg(short, long, default_value = "3")]
depth: usize,
/// Show hidden files and directories
#[arg(long)]
hidden: bool,
/// Show file sizes
#[arg(long)]
sizes: bool,
/// Filter by file extensions (e.g., "rs,py,js")
#[arg(long)]
extensions: Option<String>,
/// Ignore .gitignore rules (show all files, default respects .gitignore)
#[arg(long)]
no_ignore: bool,
},
/// Search for AST patterns in source code
Search {
/// AST pattern to search for
pattern: String,
/// Target files or directories
#[arg(short, long)]
files: Vec<PathBuf>,
/// Programming language (rust, javascript, python, etc.)
#[arg(short, long)]
language: Option<String>,
/// Include context lines around matches
#[arg(short, long, default_value = "3")]
context: usize,
/// Maximum number of matches to return
#[arg(short, long, default_value = "1000")]
max_matches: usize,
/// Output format (human, json)
#[arg(long, default_value = "human")]
format: String,
},
/// Transform code using AST-based patterns
Transform {
/// AST pattern to match
pattern: String,
/// Replacement text or pattern
replacement: String,
/// Target files or directories
#[arg(short, long)]
files: Vec<PathBuf>,
/// Programming language (rust, javascript, python, etc.)
#[arg(short, long)]
language: Option<String>,
/// Show what would be changed without making changes
#[arg(long)]
dry_run: bool,
/// Skip creating backup files before transformation
#[arg(long)]
no_backup: bool,
/// Maximum changes per file
#[arg(long, default_value = "100")]
max_changes: usize,
},
/// List common patterns for a programming language
Patterns {
/// Programming language to show patterns for
language: String,
/// Pattern category (search or transform)
#[arg(long, default_value = "search")]
category: String,
},
/// Analyze code structure and generate insights
Analyze {
/// Target files or directories
#[arg(short, long)]
files: Vec<PathBuf>,
/// Programming language (rust, javascript, python, etc.)
#[arg(short, long)]
language: Option<String>,
/// Analysis type (structure, complexity, patterns)
#[arg(long, default_value = "structure")]
analysis_type: String,
/// Output format (human, json)
#[arg(long, default_value = "human")]
format: String,
},
}
#[derive(Subcommand, Debug)]
enum ArtifactAction {
/// List all session-generated artifacts with metadata
List {
/// Filter by artifact type (logs, generated, outputs, templates, diagrams)
#[arg(short, long)]
artifact_type: Option<String>,
/// Filter by session ID
#[arg(short, long)]
session: Option<String>,
/// Show artifacts from last N sessions
#[arg(short, long)]
recent: Option<u32>,
/// Show detailed artifact metadata
#[arg(short, long)]
verbose: bool,
},
/// Clean up old or unused artifacts
Clean {
/// Artifact types to clean (logs, generated, outputs, all)
#[arg(short, long, default_value = "logs")]
artifact_type: String,
/// Show what would be cleaned without removing
#[arg(long)]
dry_run: bool,
/// Force cleanup without confirmation
#[arg(short, long)]
force: bool,
},
/// Archive session artifacts to compressed storage
Archive {
/// Session ID to archive
session_id: String,
/// Archive format (tar.gz, zip)
#[arg(short, long, default_value = "tar.gz")]
format: String,
/// Output directory for archive
#[arg(short, long)]
output: Option<String>,
/// Remove original files after archiving
#[arg(long)]
remove_originals: bool,
},
/// Track and organize current session artifacts
Organize {
/// Create directories and categorize current session files
#[arg(long)]
categorize: bool,
/// Generate artifact manifest for current session
#[arg(long)]
manifest: bool,
/// Tag artifacts with metadata
#[arg(long)]
tag: bool,
},
/// Search artifacts by content, name, or metadata
Search {
/// Search query
query: String,
/// Search in artifact contents
#[arg(short, long)]
content: bool,
/// Search artifact names/paths only
#[arg(short, long)]
names: bool,
/// Limit results
#[arg(short, long, default_value = "20")]
limit: u32,
},
/// Show detailed information about specific artifact
Show {
/// Artifact path or ID
artifact_path: String,
/// Show artifact content
#[arg(short, long)]
content: bool,
/// Show artifact metadata
#[arg(short, long)]
metadata: bool,
},
/// Export artifacts with metadata for backup or sharing
Export {
/// Session IDs to export
#[arg(short, long)]
sessions: Vec<String>,
/// Export format (json, yaml, csv)
#[arg(short, long, default_value = "json")]
format: String,
/// Output file path
#[arg(short, long)]
output: Option<String>,
/// Include artifact contents in export
#[arg(long)]
include_content: bool,
},
}
#[derive(Subcommand, Debug)]
enum ContinuityAction {
/// Save current session context for continuity
Save {
/// Session ID to save context for
session_id: String,
/// Session focus description
#[arg(short, long)]
focus: String,
/// Additional context notes
#[arg(short, long)]
notes: Option<String>,
},
/// Load session context from previous session
Load {
/// Session ID to load context from
session_id: String,
/// Output format (summary, detailed, json)
#[arg(short, long, default_value = "summary")]
format: String,
},
/// Transfer knowledge between sessions
Transfer {
/// Source session ID
from_session: String,
/// Target session ID
to_session: String,
/// Confirm transfer without prompt
#[arg(short, long)]
force: bool,
},
/// List session continuity states
List {
/// Filter by project ID
#[arg(short, long)]
project: Option<String>,
/// Output format (table, json)
#[arg(short, long, default_value = "table")]
format: String,
},
/// Create context snapshot for current project state
Snapshot {
/// Project ID to snapshot (default: current project)
#[arg(short, long)]
project: Option<String>,
/// Output format (summary, json)
#[arg(short, long, default_value = "summary")]
format: String,
},
}
#[derive(Subcommand, Debug)]
enum VersionAction {
/// Display current version with calculation breakdown
Show {
/// Show detailed calculation breakdown
#[arg(long)]
verbose: bool,
/// Output format (human, json)
#[arg(short, long, default_value = "human")]
format: String,
},
/// Set the major version number (stored in database)
Major {
/// Major version number to set
version: u32,
},
/// Create git tag with current calculated version
Tag {
/// Tag prefix (default: 'v')
#[arg(long, default_value = "v")]
prefix: String,
/// Tag message
#[arg(short, long)]
message: Option<String>,
},
/// Show version calculation information and git compatibility
Info {
/// Include git history analysis
#[arg(long)]
include_history: bool,
},
}
#[derive(Subcommand, Debug)]
enum WstemplateAction {
/// Set the scan root for this project (replaces any existing entry)
Add {
/// Directory tree to scan for .wstemplate files and peer .wsb/state.json files
path: PathBuf,
/// Override the auto-derived alias (must be a valid Tera identifier: [a-z_][a-z0-9_]*)
#[arg(long)]
alias: Option<String>,
},
/// Remove this project's wstemplate entry
Remove {
/// Alias of the entry to remove (must match current entry)
alias: String,
},
/// Show this project's wstemplate entry (alias and scan root)
ListEntries,
/// List all .wstemplate files relevant to this project (no rendering)
List,
/// Render all .wstemplate files relevant to this project
Render,
}
#[derive(Subcommand, Debug)]
enum ScrapCommands {
/// List contents of .scrap folder
#[command(alias = "ls")]
List {
/// Sort by: name, date, size
#[arg(short, long, default_value = "date")]
sort: String,
},
/// Clean old items from .scrap folder
Clean {
/// Remove items older than N days
#[arg(short, long, default_value = "30")]
days: u64,
/// Show what would be removed without actually removing
#[arg(short = 'n', long)]
dry_run: bool,
},
/// Remove all items from .scrap folder
Purge {
/// Skip confirmation prompt
#[arg(short, long)]
force: bool,
},
/// Search for files in .scrap
Find {
/// Search pattern (regex supported)
pattern: String,
/// Also search file contents
#[arg(short, long)]
content: bool,
},
/// Create archive of .scrap contents
Archive {
/// Output archive path
#[arg(short, long)]
output: Option<std::path::PathBuf>,
/// Remove files after archiving
#[arg(short, long)]
remove: bool,
},
}
fn main() {
// Initialize logging as early as possible
let debug_mode = std::env::args().any(|arg| arg == "--debug" || arg == "-v" || arg == "--verbose");
if let Err(e) = logging::setup_logging(debug_mode) {
eprintln!("Failed to initialize logging: {}", e);
}
// Log version and startup
log_version_info(env!("CARGO_PKG_VERSION"), option_env!("GIT_HASH"));
log::info!("Starting workspace tool suite");
// Setup shell completions on first run (but not when running as a git hook)
if !is_running_as_git_hook() {
if let Err(e) = setup_shell_completions() {
log_warning("Shell completions", &format!("Failed to setup: {}", e));
}
}
if let Err(e) = run() {
log::error!("Application error: {:#}", e);
eprintln!("{}: {:#}", "Error".red(), e);
process::exit(1);
}
log::info!("Workspace tool completed successfully");
}
fn run() -> Result<()> {
let start_time = Instant::now();
let args = Args::parse();
log::debug!("Parsed command line arguments: {:?}", args);
match args.command {
Commands::Refactor { args } => {
log_operation_start("refactor", &format!("root: {:?}", args.root_dir));
match wsb::run_refac(args) {
Ok(()) => log_operation_complete("refactor", start_time.elapsed()),
Err(error) => {
log_operation_error("refactor", &error);
eprintln!("{}: {:#}", "Error".red(), error);
process::exit(1);
}
}
}
Commands::Git { command } => {
run_git_command(command)?;
}
Commands::Template { action } => {
handle_template_command(action)?;
}
Commands::Update { no_git, git_add, tag } => {
log_operation_start("update", &format!("no_git: {}, git_add: {}, tag: {:?}", no_git, git_add, tag));
update_state(no_git, git_add, tag.as_deref())?;
log_operation_complete("update", start_time.elapsed());
}
Commands::Scrap { paths, command } => {
run_scrap_command(paths, command)?;
}
Commands::Unscrap { name, force, to } => {
run_unscrap_command(name, force, to)?;
}
Commands::Ldiff { substitute_char } => {
run_ldiff_command(substitute_char)?;
}
Commands::Code { action } => {
let action = action.unwrap_or(CodeAction::Tree {
depth: 3,
hidden: false,
sizes: false,
extensions: None,
no_ignore: false
});
handle_code_command(action)?;
}
Commands::Test { dry_run, install, args } => {
handle_test_command(dry_run, install, args)?;
}
Commands::McpServer { port, debug, migrate } => {
run_mcp_server(port, debug, migrate)?;
}
Commands::Sample { project, data, force, output } => {
run_sample_command(project, data, force, output)?;
}
Commands::Start { continue_from, debug_mode, project_setup, first_task } => {
run_start_command(continue_from, debug_mode, project_setup, first_task)?;
}
Commands::End { summary, debug_mode, force, skip_docs } => {
run_end_command(summary, debug_mode, force, skip_docs)?;
}
Commands::Artifacts { action: _ } => {
println!("โ ๏ธ Artifacts command temporarily disabled - F0159 needs proper implementation");
println!(" This feature is marked incomplete and requires full rewrite");
}
Commands::Consolidate { debug_mode, force, generate_diagrams, preserve_complexity } => {
run_consolidate_command(debug_mode, force, generate_diagrams, preserve_complexity)?;
}
Commands::Status { debug_mode, include_features, include_metrics, format } => {
run_status_command(debug_mode, include_features, include_metrics, format)?;
}
Commands::Task { action } => {
run_task_command(action)?;
}
Commands::Directive { action } => {
run_directive_command(action)?;
}
Commands::Feature { action } => {
run_feature_command(action)?;
}
Commands::Relationship { action } => {
run_relationship_command(action)?;
}
Commands::Note { action } => {
run_note_command(action)?;
}
Commands::Database { action } => {
run_database_command(action)?;
}
Commands::Continuity { action } => {
run_continuity_command(action)?;
}
Commands::Version { action } => {
run_version_command(action)?;
}
Commands::Wstemplate { action } => {
handle_wstemplate_command(action)?;
}
}
Ok(())
}
fn run_git_command(command: Option<GitCommands>) -> Result<()> {
match command {
Some(GitCommands::Install { force }) => install_hook(force)?,
Some(GitCommands::Uninstall) => uninstall_hook()?,
Some(GitCommands::Show) => show_version()?,
Some(GitCommands::Status) => show_status()?,
Some(GitCommands::Diff { base, output, staged }) => {
let cwd = std::env::current_dir()
.context("Failed to get current directory")?;
let outdir = std::path::Path::new(&output);
let files = wsb::fleet::commands::diff::generate_per_file_diffs(
&cwd,
base.as_deref(),
outdir,
staged,
).map_err(|e| anyhow::anyhow!("{}", e))?;
if files.is_empty() {
println!("{}: No changes to diff", "Info".blue());
} else {
println!("{}: {} diff file(s) written to '{}'", "Done".green(), files.len(), output);
for f in &files {
println!(" {}.diff", f);
}
}
}
Some(GitCommands::Fleet { command, verbose, dry_run }) => {
wsb::fleet::execute(command, verbose, dry_run)?;
}
None => {
// Default behavior: install hook if not installed, otherwise update state
if !is_git_repository() {
log::warn!("Git command attempted outside git repository");
eprintln!("{}: Not in a git repository", "Warning".yellow());
eprintln!("{}: Use 'wsb git install' to set up version management", "Tip".blue());
return Ok(());
}
if !is_hook_installed()? {
log::info!("Git hook not installed, installing automatically");
eprintln!("{}: Git hook not installed", "Info".blue());
eprintln!("{}: Installing pre-commit hook for automatic version management", "Info".blue());
install_hook(false)?;
} else {
// Hook is installed, just update state
let project_root = get_project_root()?;
let config = St8Config::load(&project_root)?;
let workspace_state = WorkspaceState::load(&project_root)?;
let version_info = calculate_version(&project_root)?;
update_version_file(&version_info, &config)?;
// Render wstemplate files if configured
if let Some(entry) = workspace_state.wstemplate_entry() {
let engine = WstemplateEngine::new(
version_info,
workspace_state.project_name.clone(),
entry.alias.clone(),
project_root.clone(),
entry.root.clone(),
);
engine.render_relevant()?;
}
workspace_state.save(&project_root)?;
}
}
}
Ok(())
}
fn update_state(no_git: bool, git_add: bool, tag: Option<&str>) -> Result<()> {
let project_root = get_project_root()?;
let config = St8Config::load(&project_root)?;
let workspace_state = WorkspaceState::load(&project_root)?;
// Validate tag if provided
if let Some(t) = tag {
wsb::st8::validate_git_tag(t)?;
}
// Calculate version once
let mut version_info = calculate_version(&project_root)?;
// Append tag suffix to version if provided
if let Some(t) = tag {
version_info.full_version = format!("{}_{}", version_info.full_version, t);
}
// Write version.txt first โ other projects read our version.txt
// when resolving {{ projects.OUR_ALIAS.version }}
update_version_file(&version_info, &config)?;
if !config.version_file.is_empty() {
log::info!("Updated version file: {}", config.version_file);
println!("{}: Updated {}", "Info".blue(), config.version_file);
}
// Render .tera templates via TemplateManager
let template_manager = TemplateManager::new(&workspace_state)?;
let project_name = workspace_state.project_name.as_deref();
let rendered_tera_files = template_manager.render_all_templates(&version_info, project_name)?;
if !rendered_tera_files.is_empty() {
log::info!("Rendered {} .tera templates: {}", rendered_tera_files.len(), rendered_tera_files.join(", "));
}
// Save full_version before version_info is moved into WstemplateEngine
let full_version = version_info.full_version.clone();
// Render .wstemplate files via WstemplateEngine
let mut rendered_wstemplate_files: Vec<String> = Vec::new();
if let Some(entry) = workspace_state.wstemplate_entry() {
let engine = WstemplateEngine::new(
version_info,
workspace_state.project_name.clone(),
entry.alias.clone(),
project_root.clone(),
entry.root.clone(),
);
let rendered = engine.render_relevant()?;
for r in &rendered {
log::info!("wstemplate: {} -> {}", r.source_path.display(), r.output_path.display());
rendered_wstemplate_files.push(r.output_path.display().to_string());
}
if !rendered.is_empty() {
println!("{}: Rendered {} .wstemplate files", "Info".blue(), rendered.len());
}
}
// Save state
workspace_state.save(&project_root)?;
// Add files to git if requested and we're in a git repository
if !no_git && git_add && is_git_repository() {
let mut files_to_add = Vec::new();
if !config.version_file.is_empty() {
files_to_add.push(config.version_file.clone());
}
for file in &rendered_tera_files {
files_to_add.push(file.clone());
}
for file in &rendered_wstemplate_files {
files_to_add.push(file.clone());
}
if !files_to_add.is_empty() {
let added_files = add_files_to_git(&files_to_add)?;
if !added_files.is_empty() {
log::info!("Added {} files to git staging area: {}", added_files.len(), added_files.join(", "));
println!("{}: Added {} files to git staging area", "Info".blue(), added_files.len());
for file in &added_files {
println!(" - {}", file);
}
}
}
}
// Create git tag if --tag was provided
if let Some(t) = tag {
if is_git_repository() {
let tag_name = format!("v{}", full_version);
let tag_message = format!("Tagged version {}", full_version);
let output = Command::new("git")
.args(["tag", "-a", &tag_name, "-m", &tag_message])
.output()
.context("Failed to create git tag")?;
if !output.status.success() {
let stderr = String::from_utf8_lossy(&output.stderr);
anyhow::bail!("Failed to create git tag '{}': {}", tag_name, stderr);
}
log::info!("Created git tag: {} (tag: {})", tag_name, t);
println!("{}: Created git tag: {}", "Tag".green(), tag_name.green().bold());
} else {
anyhow::bail!("--tag requires a git repository");
}
}
Ok(())
}
fn handle_wstemplate_command(action: WstemplateAction) -> Result<()> {
let project_root = get_project_root()?;
let mut workspace_state = WorkspaceState::load(&project_root)?;
match action {
WstemplateAction::Add { path, alias } => {
let scan_root = path.canonicalize()
.with_context(|| format!("Cannot resolve path: {}", path.display()))?;
let alias = alias.unwrap_or_else(|| {
WstemplateEngine::derive_alias(&project_root)
});
// Validate alias is a valid Tera identifier
anyhow::ensure!(
!alias.is_empty()
&& alias.chars().all(|c| c.is_ascii_alphanumeric() || c == '_')
&& !alias.starts_with(|c: char| c.is_ascii_digit()),
"Invalid alias '{}': must match [a-z_][a-z0-9_]* (Tera identifier)",
alias
);
let entry = WstemplateEntry {
alias: alias.clone(),
root: scan_root.clone(),
};
workspace_state.set_wstemplate_entry(entry);
workspace_state.save(&project_root)?;
println!("{} wstemplate entries:", "1".bold());
println!(" {} \u{2192} {}", alias, scan_root.display());
}
WstemplateAction::Remove { alias } => {
match workspace_state.wstemplate_entry() {
Some(entry) if entry.alias == alias => {
workspace_state.clear_wstemplate_entry();
workspace_state.save(&project_root)?;
println!("Removed wstemplate entry '{}'", alias);
}
Some(entry) => {
anyhow::bail!(
"No entry with alias '{}'. This project's alias is '{}'.",
alias,
entry.alias
);
}
None => {
anyhow::bail!("No wstemplate entry configured for this project.");
}
}
}
WstemplateAction::ListEntries => {
match workspace_state.wstemplate_entry() {
Some(entry) => {
println!("{} wstemplate entries:", "1".bold());
println!(" {} \u{2192} {}", entry.alias, entry.root.display());
}
None => {
println!("{} wstemplate entries:", "0".bold());
}
}
}
WstemplateAction::List => {
let entry = workspace_state.wstemplate_entry()
.ok_or_else(|| anyhow::anyhow!(
"No wstemplate entry configured. Run 'wsb wstemplate add <scan-root>' first."
))?;
let version_info = calculate_version(&project_root)?;
let engine = WstemplateEngine::new(
version_info,
workspace_state.project_name.clone(),
entry.alias.clone(),
project_root.clone(),
entry.root.clone(),
);
let project_roots = engine.discover_project_roots()?;
let templates = engine.discover_relevant(&project_roots)?;
if templates.is_empty() {
println!("No relevant .wstemplate files found.");
} else {
println!("{} relevant .wstemplate files:", templates.len());
for path in &templates {
println!(" {}", path.display());
}
}
}
WstemplateAction::Render => {
let entry = workspace_state.wstemplate_entry()
.ok_or_else(|| anyhow::anyhow!(
"No wstemplate entry configured. Run 'wsb wstemplate add <scan-root>' first."
))?;
let version_info = calculate_version(&project_root)?;
let engine = WstemplateEngine::new(
version_info,
workspace_state.project_name.clone(),
entry.alias.clone(),
project_root.clone(),
entry.root.clone(),
);
let rendered = engine.render_relevant()?;
if rendered.is_empty() {
println!("No templates to render.");
} else {
println!("Rendered {} templates:", rendered.len());
for r in &rendered {
println!(" {} \u{2192} {}", r.source_path.display(), r.output_path.display());
}
}
}
}
Ok(())
}
fn run_scrap_command(paths: Vec<std::path::PathBuf>, command: Option<ScrapCommands>) -> Result<()> {
let mut args = Vec::new();
// Convert clap ScrapCommands to original scrap binary arguments
match command {
Some(ScrapCommands::List { sort }) => {
args.push("list".to_string());
args.push("--sort".to_string());
args.push(sort);
}
Some(ScrapCommands::Clean { days, dry_run }) => {
args.push("clean".to_string());
args.push("--days".to_string());
args.push(days.to_string());
if dry_run {
args.push("--dry-run".to_string());
}
}
Some(ScrapCommands::Purge { force }) => {
args.push("purge".to_string());
if force {
args.push("--force".to_string());
}
}
Some(ScrapCommands::Find { pattern, content }) => {
args.push("find".to_string());
args.push(pattern);
if content {
args.push("--content".to_string());
}
}
Some(ScrapCommands::Archive { output, remove }) => {
args.push("archive".to_string());
if let Some(output_path) = output {
args.push("--output".to_string());
args.push(output_path.to_string_lossy().to_string());
}
if remove {
args.push("--remove".to_string());
}
}
None => {
// Add all paths as arguments
for path in paths {
args.push(path.to_string_lossy().to_string());
}
}
}
wsb::run_scrap(args)
}
fn run_unscrap_command(name: Option<String>, force: bool, to: Option<std::path::PathBuf>) -> Result<()> {
let mut args = Vec::new();
if let Some(item_name) = name {
args.push(item_name);
}
if force {
args.push("--force".to_string());
}
if let Some(target_path) = to {
args.push("--to".to_string());
args.push(target_path.to_string_lossy().to_string());
}
wsb::run_unscrap(args)
}
fn run_ldiff_command(substitute_char: String) -> Result<()> {
wsb::run_ldiff(vec![substitute_char.clone()])
}
fn install_hook(force: bool) -> Result<()> {
if !is_git_repository() {
log::warn!("install_hook called outside git repository");
eprintln!("{}: Not in a git repository", "Error".red());
eprintln!("{}: Navigate to a git repository and try again", "Tip".yellow());
return Ok(());
}
let git_root = get_git_root()?;
let hooks_dir = git_root.join(".git").join("hooks");
let hook_file = hooks_dir.join("pre-commit");
// Create hooks directory if it doesn't exist
if !hooks_dir.exists() {
fs::create_dir_all(&hooks_dir)
.context("Failed to create git hooks directory")?;
log::info!("Created git hooks directory: {}", hooks_dir.display());
}
// Check if already installed
if !force && is_hook_installed()? {
println!("{} Git hook is already installed", "Info".blue());
println!("{} Use 'wsb git install --force' to reinstall", "Tip".yellow());
return Ok(());
}
// Get current binary path
let current_exe = env::current_exe()
.context("Failed to get current executable path")?;
let st8_block = format!(
"#!/bin/bash\n# === WS BLOCK START ===\n# DO NOT EDIT THIS BLOCK MANUALLY\n# Use 'wsb git uninstall' to remove this hook\n{} update --git-add\n# === WS BLOCK END ===\n",
current_exe.display()
);
if hook_file.exists() {
// Read existing hook content
let existing_content = fs::read_to_string(&hook_file)
.context("Failed to read existing pre-commit hook")?;
// Remove any existing st8 block
let cleaned_content = remove_st8_block(&existing_content);
// Append new st8 block
let new_content = if cleaned_content.trim().is_empty() {
st8_block
} else {
format!("{}\n{}", cleaned_content.trim_end(), st8_block)
};
fs::write(&hook_file, new_content)
.context("Failed to update pre-commit hook")?;
log::info!("Updated existing pre-commit hook: {}", hook_file.display());
} else {
// Create new hook file
fs::write(&hook_file, &st8_block)
.context("Failed to create pre-commit hook")?;
log::info!("Created new pre-commit hook: {}", hook_file.display());
}
// Make hook executable on Unix systems
#[cfg(unix)]
{
use std::os::unix::fs::PermissionsExt;
let mut perms = fs::metadata(&hook_file)?.permissions();
perms.set_mode(0o755);
fs::set_permissions(&hook_file, perms)?;
}
log::info!("Git pre-commit hook installed successfully at {}", hook_file.display());
println!("{} Git hook installed successfully", "Success".green());
println!("{} Version will be updated automatically on each commit", "Info".blue());
Ok(())
}
fn uninstall_hook() -> Result<()> {
if !is_git_repository() {
log::warn!("uninstall_hook called outside git repository");
eprintln!("{}: Not in a git repository", "Error".red());
return Ok(());
}
let git_root = get_git_root()?;
let hook_file = git_root.join(".git").join("hooks").join("pre-commit");
if !hook_file.exists() {
println!("{} No pre-commit hook found", "Info".blue());
return Ok(());
}
let content = fs::read_to_string(&hook_file)
.context("Failed to read pre-commit hook")?;
if !content.contains("=== WS BLOCK START ===") {
println!("{} No st8 hook block found in pre-commit hook", "Info".blue());
return Ok(());
}
let cleaned_content = remove_st8_block(&content);
if cleaned_content.trim().is_empty() {
// Remove the entire hook file if only st8 content
fs::remove_file(&hook_file)
.context("Failed to remove pre-commit hook")?;
println!("{} Removed pre-commit hook", "Success".green());
log::info!("Removed pre-commit hook: {}", hook_file.display());
} else {
// Write back the cleaned content
fs::write(&hook_file, cleaned_content.trim_end())
.context("Failed to update pre-commit hook")?;
println!("{} Removed st8 from pre-commit hook", "Success".green());
log::info!("Removed st8 block from pre-commit hook: {}", hook_file.display());
}
Ok(())
}
fn show_version() -> Result<()> {
let project_root = get_project_root()?;
let workspace_state = WorkspaceState::load(&project_root)?;
println!("{}", "Version Information".bold().underline());
println!();
let version_info = VersionInfo::calculate()?;
println!("{}: {}", "Current Version".blue(), version_info.full_version);
println!("{}: {}", "Project Name".blue(), workspace_state.project_name.as_deref().unwrap_or("Unknown"));
if is_git_repository() {
println!("{}: {}", "Major Version".blue(), version_info.major_version);
println!("{}: {}", "Minor Version".blue(), version_info.minor_version);
println!("{}: {}", "Patch Version".blue(), version_info.patch_version);
// Note: VersionInfo doesn't track dirty state in this implementation
}
let config = St8Config::load(&project_root)?;
if !config.version_file.is_empty() {
println!("{}: {}", "Version File".blue(), &config.version_file);
}
let detected_files = detect_project_files(&project_root);
if let Ok(detected_files) = detected_files {
if !detected_files.is_empty() {
println!();
println!("{}", "Detected Project Files:".bold());
for file in detected_files {
println!(" - {}", file.path.display());
}
}
}
Ok(())
}
fn show_status() -> Result<()> {
if !is_git_repository() {
println!("{}: Not in a git repository", "Status".red());
return Ok(());
}
let git_root = get_git_root()?;
let config = St8Config::load(&git_root)?;
println!("{}", "Git Integration Status".bold().underline());
println!();
// Hook status
if is_hook_installed()? {
println!("{}: Installed โ", "Pre-commit Hook".green());
} else {
println!("{}: Not installed โ", "Pre-commit Hook".red());
println!("{}: Run 'wsb git install' to set up automatic version management", "Tip".yellow());
}
// Version file status
if !config.version_file.is_empty() {
let version_path = git_root.join(&config.version_file);
if version_path.exists() {
println!("{}: {} โ", "Version File".green(), config.version_file);
} else {
println!("{}: {} (not found)", "Version File".yellow(), config.version_file);
}
} else {
println!("{}: Auto-detected", "Version File".blue());
}
// Show current version
let workspace_state = WorkspaceState::load(&git_root)?;
let version_info = VersionInfo::calculate()?;
println!("{}: {}", "Current Version".blue(), version_info.full_version);
// Template status
let template_manager = TemplateManager::new(&workspace_state)?;
let templates = template_manager.list_templates();
let enabled_count = templates.iter().filter(|t| t.enabled).count();
if !templates.is_empty() {
log::info!("Templates: {} total, {} enabled", templates.len(), enabled_count);
} else {
println!("{}: None configured", "Templates".blue());
}
Ok(())
}
fn calculate_version(project_root: &std::path::Path) -> Result<VersionInfo> {
let db_path = project_root.join(".wsb/project.db");
let rt = tokio::runtime::Runtime::new()?;
let version_info = rt.block_on(async {
let pool = wsb::entities::database::initialize_database(&db_path).await?;
let major_version = get_project_major_version(&pool).await?;
wsb::st8::VersionInfo::calculate_with_major(major_version)
})?;
log::info!("Version calculated: {}", version_info.full_version);
Ok(version_info)
}
fn log_action(message: &str) {
match log_to_file(message) {
Ok(_) => {}
Err(_) => {}
}
}
fn log_to_file(message: &str) -> Result<()> {
// Use structured logging instead of custom file logging
log::info!("{}", message);
Ok(())
}
fn handle_template_command(action: TemplateAction) -> Result<()> {
let rt = tokio::runtime::Runtime::new()?;
rt.block_on(async {
match action {
TemplateAction::GenerateDocs { doc_type, output, force } => {
handle_generate_docs(&doc_type, output.as_deref(), force).await
}
TemplateAction::InitDocs { force } => {
handle_init_docs(force)
}
TemplateAction::List => {
println!("Template management not yet implemented");
Ok(())
}
_ => {
println!("Template command not yet implemented in new schema");
Ok(())
}
}
})
}
async fn handle_generate_docs(doc_type: &str, output_dir: Option<&str>, force: bool) -> Result<()> {
use tera::Tera;
use std::collections::HashMap;
let db_path = get_project_root()?.join(".wsb/project.db");
let pool = wsb::entities::database::initialize_database(&db_path).await?;
let entity_manager = EntityManager::new(pool.clone());
// Get current project
let project = entity_manager.get_current_project().await?
.ok_or_else(|| anyhow::anyhow!("No active project found. Run 'wsb sample --create' first."))?;
// Get all entities for template context
let features = entity_manager.list_features().await?;
let sessions = entity_manager.list_sessions_by_project(&project.id).await?;
let tasks = entity_manager.list_tasks().await?;
// Calculate metrics
let total_features = features.len();
let implemented_features = features.iter().filter(|f| matches!(f.state.as_str(), "implemented_no_tests" | "implemented_failing_tests" | "implemented_passing_tests")).count();
let tested_features = features.iter().filter(|f| matches!(f.state.as_str(), "implemented_passing_tests" | "implemented_failing_tests" | "tests_broken")).count();
let implementation_percentage = if total_features > 0 { (implemented_features * 100) / total_features } else { 0 };
let test_percentage = if total_features > 0 { (tested_features * 100) / total_features } else { 0 };
// Setup Tera template engine with embedded templates
let mut tera = Tera::new("src/templates/*.tera")?;
// Add custom filters
tera.register_filter("feature_state_emoji", |value: &tera::Value, _: &HashMap<String, tera::Value>| {
match value.as_str().unwrap_or("") {
"not_implemented" => Ok(tera::Value::String("โ".to_string())),
"implemented_no_tests" => Ok(tera::Value::String("๐ ".to_string())),
"implemented_failing_tests" => Ok(tera::Value::String("๐ก".to_string())),
"implemented_passing_tests" => Ok(tera::Value::String("๐ข".to_string())),
"tests_broken" => Ok(tera::Value::String("โ ๏ธ".to_string())),
"critical_issue" => Ok(tera::Value::String("๐ด".to_string())),
_ => Ok(tera::Value::String("โ".to_string())),
}
});
let output_path = output_dir.unwrap_or(".");
match doc_type {
"all" => {
generate_claude_md(&tera, &project, &features, &sessions, &tasks,
implementation_percentage, test_percentage, output_path, force).await?;
generate_features_md(&tera, &project, &features, total_features,
implementation_percentage, test_percentage, output_path, force).await?;
generate_progress_md(&tera, &sessions, output_path, force).await?;
generate_status_report(&project, &features, &tasks, &sessions,
implementation_percentage, test_percentage, output_path, force).await?;
}
"claude" => {
generate_claude_md(&tera, &project, &features, &sessions, &tasks,
implementation_percentage, test_percentage, output_path, force).await?;
}
"features" => {
generate_features_md(&tera, &project, &features, total_features,
implementation_percentage, test_percentage, output_path, force).await?;
}
"progress" => {
generate_progress_md(&tera, &sessions, output_path, force).await?;
}
"status" => {
generate_status_report(&project, &features, &tasks, &sessions,
implementation_percentage, test_percentage, output_path, force).await?;
}
_ => {
anyhow::bail!("Unknown documentation type: {}. Use 'claude', 'features', 'progress', 'status', or 'all'", doc_type);
}
}
println!("โ
Documentation generation complete for type: {}", doc_type);
Ok(())
}
async fn generate_claude_md(
tera: &tera::Tera,
project: &wsb::entities::schema_models::Project,
features: &[wsb::entities::schema_models::Feature],
sessions: &[wsb::entities::schema_models::Session],
_tasks: &[wsb::entities::schema_models::Task],
implementation_percentage: usize,
test_percentage: usize,
output_path: &str,
force: bool
) -> Result<()> {
let mut context = tera::Context::new();
context.insert("project", project);
context.insert("features", features);
context.insert("recent_sessions", &sessions.iter().take(3).collect::<Vec<_>>());
context.insert("total_features", &features.len());
context.insert("implemented_features", &features.iter().filter(|f| matches!(f.state.as_str(), "implemented_no_tests" | "implemented_failing_tests" | "implemented_passing_tests")).count());
context.insert("tested_features", &features.iter().filter(|f| matches!(f.state.as_str(), "implemented_passing_tests")).count());
context.insert("implementation_percentage", &implementation_percentage);
context.insert("test_percentage", &test_percentage);
context.insert("generated_at", &chrono::Utc::now());
let rendered = tera.render("claude_md.tera", &context)?;
let output_file = std::path::Path::new(output_path).join("CLAUDE.md");
write_doc_file(&output_file, &rendered, force)?;
println!("Generated: {}", output_file.display());
Ok(())
}
async fn generate_features_md(
tera: &tera::Tera,
project: &wsb::entities::schema_models::Project,
features: &[wsb::entities::schema_models::Feature],
total_features: usize,
implementation_percentage: usize,
test_percentage: usize,
output_path: &str,
force: bool
) -> Result<()> {
use std::collections::BTreeMap;
let mut context = tera::Context::new();
context.insert("project", project);
context.insert("features", features);
context.insert("total_features", &total_features);
context.insert("implementation_percentage", &implementation_percentage);
context.insert("test_percentage", &test_percentage);
context.insert("generated_at", &chrono::Utc::now());
// Group features by category
let mut features_by_category: BTreeMap<String, Vec<&wsb::entities::schema_models::Feature>> = BTreeMap::new();
for feature in features {
let category = feature.category.as_deref().unwrap_or("General").to_string();
features_by_category.entry(category).or_insert_with(Vec::new).push(feature);
}
context.insert("features_by_category", &features_by_category);
// Feature state counts
let mut feature_counts = HashMap::new();
for feature in features {
let state_name = format!("{:?}", feature.state);
*feature_counts.entry(state_name).or_insert(0) += 1;
}
context.insert("feature_counts", &feature_counts);
let rendered = tera.render("features_md.tera", &context)?;
let output_file = std::path::Path::new(output_path).join("internal").join("FEATURES.md");
if let Some(parent) = output_file.parent() {
std::fs::create_dir_all(parent)?;
}
write_doc_file(&output_file, &rendered, force)?;
println!("Generated: {}", output_file.display());
Ok(())
}
async fn generate_progress_md(
_tera: &tera::Tera,
sessions: &[wsb::entities::schema_models::Session],
output_path: &str,
force: bool
) -> Result<()> {
let mut content = String::new();
content.push_str("# Progress Tracking - Database Generated\n\n");
content.push_str(&format!("**Generated**: {}\n", chrono::Utc::now().format("%Y-%m-%d %H:%M:%S UTC")));
content.push_str("**Source**: Database-driven documentation generation\n\n");
content.push_str("## Recent Sessions\n\n");
for session in sessions.iter().take(5) {
content.push_str(&format!("### {}\n", &session.title));
content.push_str(&format!("**Status**: {}\n", &session.status));
content.push_str(&format!("**Date**: {}\n", &session.date));
if let Some(start_time) = &session.start_time {
content.push_str(&format!("**Started**: {} {}\n", &session.date, start_time));
}
if let Some(end_time) = &session.end_time {
content.push_str(&format!("**Ended**: {} {}\n", &session.date, end_time));
}
content.push_str("\n");
}
content.push_str("---\n\n*Generated from database entities*\n");
let output_file = std::path::Path::new(output_path).join("internal").join("PROGRESS_TRACKING.md");
if let Some(parent) = output_file.parent() {
std::fs::create_dir_all(parent)?;
}
write_doc_file(&output_file, &content, force)?;
println!("Generated: {}", output_file.display());
Ok(())
}
async fn generate_status_report(
project: &wsb::entities::schema_models::Project,
features: &[wsb::entities::schema_models::Feature],
tasks: &[wsb::entities::schema_models::Task],
sessions: &[wsb::entities::schema_models::Session],
implementation_percentage: usize,
test_percentage: usize,
output_path: &str,
force: bool
) -> Result<()> {
let mut content = String::new();
content.push_str("# Project Status Report\n\n");
content.push_str(&format!("**Generated**: {}\n", chrono::Utc::now().format("%Y-%m-%d %H:%M:%S UTC")));
content.push_str(&format!("**Project**: {}\n", project.name));
content.push_str(&format!("**Phase**: {}\n\n", project.current_phase.as_deref().unwrap_or("Unknown")));
content.push_str("## Implementation Status\n\n");
content.push_str(&format!("- **Total Features**: {}\n", features.len()));
content.push_str(&format!("- **Implementation Score**: {}%\n", implementation_percentage));
content.push_str(&format!("- **Test Coverage**: {}%\n", test_percentage));
content.push_str(&format!("- **Active Tasks**: {}\n", tasks.iter().filter(|t| t.status == "in_progress").count()));
content.push_str(&format!("- **Completed Tasks**: {}\n", tasks.iter().filter(|t| t.status == "completed").count()));
content.push_str(&format!("- **Total Sessions**: {}\n\n", sessions.len()));
content.push_str("## Feature State Distribution\n\n");
let mut state_counts = std::collections::HashMap::new();
for feature in features {
*state_counts.entry(&feature.state).or_insert(0) += 1;
}
for (state, count) in state_counts {
let emoji = match state.as_str() {
"not_implemented" => "โ",
"implemented_no_tests" => "๐ ",
"implemented_failing_tests" => "๐ก",
"implemented_passing_tests" => "๐ข",
"tests_broken" => "โ ๏ธ",
"critical_issue" => "๐ด",
_ => "โ",
};
content.push_str(&format!("- {} {}: {}\n", emoji, state, count));
}
content.push_str("\n---\n\n*Generated from database entities*\n");
let output_file = std::path::Path::new(output_path).join("PROJECT_STATUS.md");
write_doc_file(&output_file, &content, force)?;
println!("Generated: {}", output_file.display());
Ok(())
}
fn handle_init_docs(_force: bool) -> Result<()> {
log::info!("Documentation template initialization not yet implemented");
Ok(())
}
fn write_doc_file(file_path: &Path, content: &str, force: bool) -> Result<()> {
if file_path.exists() && !force {
eprintln!("{}: File {} already exists. Use --force to overwrite.", "Error".red(), file_path.display());
return Ok(());
}
if let Some(parent) = file_path.parent() {
std::fs::create_dir_all(parent)?;
}
std::fs::write(file_path, content)?;
Ok(())
}
fn get_project_root() -> Result<PathBuf> {
// Always use current working directory as project root
// wsb should work in any directory, even empty ones
std::env::current_dir().context("Failed to get current directory")
}
fn is_hook_installed() -> Result<bool> {
if !is_git_repository() {
return Ok(false);
}
let git_root = get_git_root()?;
let hook_file = git_root.join(".git").join("hooks").join("pre-commit");
if !hook_file.exists() {
return Ok(false);
}
let content = fs::read_to_string(&hook_file)
.context("Failed to read pre-commit hook")?;
Ok(content.contains("=== WS BLOCK START ==="))
}
fn remove_st8_block(content: &str) -> String {
let lines: Vec<&str> = content.lines().collect();
let mut result = Vec::new();
let mut in_st8_block = false;
let ends_with_newline = content.ends_with('\n');
for line in lines {
if line.contains("=== WS BLOCK START ===") {
in_st8_block = true;
continue;
}
if line.contains("=== WS BLOCK END ===") {
in_st8_block = false;
continue;
}
if !in_st8_block {
result.push(line);
}
}
let mut output = result.join("\n");
if ends_with_newline && !output.is_empty() {
output.push('\n');
}
output
}
fn is_git_repository() -> bool {
Command::new("git")
.args(&["rev-parse", "--git-dir"])
.output()
.map(|output| output.status.success())
.unwrap_or(false)
}
fn get_git_root() -> Result<PathBuf> {
let output = Command::new("git")
.args(&["rev-parse", "--show-toplevel"])
.output()
.context("Failed to execute git command")?;
if !output.status.success() {
return Err(anyhow::anyhow!("Not in a git repository"));
}
let path_str = String::from_utf8(output.stdout)
.context("Invalid UTF-8 in git output")?
.trim()
.to_string();
Ok(PathBuf::from(path_str))
}
fn add_files_to_git(files: &[String]) -> Result<Vec<String>> {
let mut added_files = Vec::new();
for file in files {
let file_path = std::path::Path::new(file);
if file_path.exists() {
let error_msg = format!("Failed to run git add for {}", file);
let status = Command::new("git")
.args(&["add", file])
.status()
.context(error_msg)?;
if status.success() {
added_files.push(file.clone());
log_action(&format!("Added file to git: {}", file));
} else {
log::warn!("Failed to add '{}' to git", file);
eprintln!("{} Failed to add '{}' to git", "Warning".yellow(), file);
}
} else {
log::warn!("File '{}' does not exist, skipping git add", file);
eprintln!("{} File '{}' does not exist, skipping git add", "Warning".yellow(), file);
}
}
Ok(added_files)
}
fn is_running_as_git_hook() -> bool {
// Check if we're running in a git hook context
// Git hooks set GIT_DIR environment variable
if env::var("GIT_DIR").is_ok() {
return true;
}
// Check for GIT_INDEX_FILE which is also set during git hooks
if env::var("GIT_INDEX_FILE").is_ok() {
return true;
}
false
}
fn setup_shell_completions() -> Result<()> {
let shell = detect_shell()?;
// Always (re)generate the completion script file.
generate_completion_file(shell)?;
// Show the activation hint exactly once per project, tracked in state.json.
show_completion_hint_if_needed(shell);
Ok(())
}
/// Show the shell-completion activation hint once per project.
///
/// Reads `.wsb/state.json` from the current directory; if it exists and
/// `completion_hint_shown` is false, prints the hint and persists the flag.
/// Errors are silently swallowed โ a broken hint must never block wsb.
fn show_completion_hint_if_needed(shell: Shell) {
let project_root = match std::env::current_dir() {
Ok(p) => p,
Err(_) => return,
};
if !project_root.join(".wsb").join("state.json").exists() {
return;
}
let mut state = match WorkspaceState::load(&project_root) {
Ok(s) => s,
Err(_) => return,
};
if state.completion_hint_shown {
return;
}
let completion_dir = match get_completion_dir(shell) {
Ok(d) => d,
Err(_) => return,
};
let completion_file = match get_completion_file_path(shell, &completion_dir) {
Ok(f) => f,
Err(_) => return,
};
print_activation_hint(shell, &completion_file);
state.completion_hint_shown = true;
let _ = state.save(&project_root);
}
fn detect_shell() -> Result<Shell> {
// Try to detect from SHELL environment variable first
if let Ok(shell) = env::var("SHELL") {
if shell.contains("bash") {
return Ok(Shell::Bash);
} else if shell.contains("zsh") {
return Ok(Shell::Zsh);
} else if shell.contains("fish") {
return Ok(Shell::Fish);
}
}
// Check for shell-specific environment variables
if env::var("ZSH_VERSION").is_ok() {
return Ok(Shell::Zsh);
} else if env::var("BASH_VERSION").is_ok() {
return Ok(Shell::Bash);
} else if env::var("FISH_VERSION").is_ok() {
return Ok(Shell::Fish);
}
// Default to bash if we can't detect
Ok(Shell::Bash)
}
/// Generate the shell completion script file (silently, no hints).
fn generate_completion_file(shell: Shell) -> Result<()> {
let mut completion_script = Vec::new();
{
let mut app = Args::command();
let name = app.get_name().to_string();
generate(shell, &mut app, name, &mut completion_script);
}
let completion_content = String::from_utf8(completion_script)
.context("Failed to convert completion script to string")?;
let completion_dir = get_completion_dir(shell)?;
fs::create_dir_all(&completion_dir)
.context("Failed to create completion directory")?;
let completion_file = get_completion_file_path(shell, &completion_dir)?;
fs::write(&completion_file, &completion_content)
.context("Failed to write completion script")?;
Ok(())
}
fn get_completion_dir(shell: Shell) -> Result<PathBuf> {
// Check XDG_DATA_HOME first, fallback to ~/.local/share
let data_home = env::var("XDG_DATA_HOME")
.unwrap_or_else(|_| {
let home = env::var("HOME").unwrap_or_default();
format!("{}/.local/share", home)
});
let dir = match shell {
Shell::Zsh => format!("{}/zsh/site-functions", data_home),
Shell::Bash => format!("{}/bash-completion/completions", data_home),
Shell::Fish => {
// Fish uses .local/share for completions like other shells
format!("{}/fish/completions", data_home)
},
Shell::PowerShell => format!("{}/powershell/completions", data_home),
_ => format!("{}/completions", data_home),
};
Ok(PathBuf::from(dir))
}
fn get_completion_file_path(shell: Shell, completion_dir: &std::path::Path) -> Result<PathBuf> {
let file_name = match shell {
Shell::Zsh => "_ws",
Shell::Bash => "wsb",
Shell::Fish => "wsb.fish",
Shell::PowerShell => "wsb.ps1",
_ => "wsb",
};
Ok(completion_dir.join(file_name))
}
/// Print the shell-completion activation hint to stderr.
///
/// Called at most once per project (gated by `completion_hint_shown` in state.json).
fn print_activation_hint(shell: Shell, file_path: &std::path::Path) {
match shell {
Shell::Bash => {
eprintln!("# To enable wsb completions, add this to your ~/.bashrc:");
eprintln!("source '{}'", file_path.to_string_lossy());
}
Shell::Zsh => {
let completion_parent = file_path.parent().unwrap_or(std::path::Path::new(""));
eprintln!("# To enable wsb completions, add these lines to your ~/.zshrc:");
eprintln!("fpath=(\"{}\" $fpath)", completion_parent.to_string_lossy());
eprintln!("autoload -U compinit && compinit");
}
Shell::Fish => {
eprintln!("# wsb completions installed to: {}", file_path.to_string_lossy());
}
Shell::PowerShell => {
eprintln!("# To enable wsb completions, add this to your PowerShell profile:");
eprintln!(". '{}'", file_path.to_string_lossy());
}
_ => {
eprintln!("# Completion script at: {}", file_path.to_string_lossy());
}
}
}
fn run_mcp_server(_port: u16, _debug: bool, migrate: bool) -> Result<()> {
tokio::runtime::Runtime::new()?.block_on(async {
if migrate {
// Migrate features from features.md to database
let features_path = std::path::Path::new("internal/features.md");
if features_path.exists() {
println!("Migrating features from {} to database...", features_path.display());
// Initialize database and entity manager
let db_path = std::env::current_dir()?.join(".wsb").join("project.db");
std::fs::create_dir_all(db_path.parent().unwrap())?;
let pool = if db_path.exists() {
sqlx::SqlitePool::connect(&format!("sqlite:{}", db_path.display())).await?
} else {
wsb::entities::database::initialize_database(&db_path).await?
};
let _entity_manager = wsb::entities::EntityManager::new(pool);
// Migration method not implemented yet
// entity_manager.migrate_features_from_file(features_path).await?;
println!("Migration completed successfully!");
return Ok(());
} else {
eprintln!("Features file not found: {}", features_path.display());
return Err(anyhow::anyhow!("Features file not found"));
}
}
// TODO: Implement MCP server when needed
println!("MCP server functionality not implemented in new schema");
Ok(())
})
}
fn run_sample_command(project: bool, data: bool, force: bool, output: String) -> Result<()> {
println!("{}", "=== Sample Project & Data Creation ===".bold().blue());
if !project && !data {
println!("{} Specify --project to create sample project structure", "๐".blue());
println!("{} Specify --data to populate database with test data", "๐๏ธ".blue());
println!("{} Use both flags to create complete sample environment", "๐".green());
return Ok(());
}
// Create output directory
let output_path = std::path::Path::new(&output);
if output_path.exists() && !force {
println!("{} Output directory '{}' already exists (use --force to overwrite)", "โ ๏ธ".yellow(), output);
return Ok(());
}
if project {
create_sample_project_in_dir(&output, force)?;
}
if data {
populate_sample_data_in_dir(&output, force)?;
}
if project && data {
log::info!("Sample project and data creation completed in: {}", output_path.display());
println!("{} Sample project and data creation completed!", "โ
".green().bold());
println!("{} Sample project created in: {}", "๐".blue(), output_path.canonicalize().unwrap_or_else(|_| output_path.to_path_buf()).display());
println!("{} To start the dashboard:", "๐ก".blue());
println!(" cd {} && wsb mcp-server --port 3000", output);
println!("{} Then access dashboard at http://localhost:3000", "๐".cyan());
}
Ok(())
}
fn create_sample_project(force: bool) -> Result<()> {
println!("{} Creating sample project structure...", "๐".blue().bold());
// Check if we're already in a project
if std::path::Path::new("CLAUDE.md").exists() && !force {
println!("{} CLAUDE.md already exists (use --force to overwrite)", "โ ๏ธ".yellow());
return Ok(());
}
// Create directories
std::fs::create_dir_all("internal")?;
std::fs::create_dir_all(".wsb")?;
std::fs::create_dir_all("src")?;
std::fs::create_dir_all("tests")?;
std::fs::create_dir_all("docs")?;
// Create CLAUDE.md
let claude_content = r#"# Sample Project
## Project Overview
**Project Name**: Sample Dashboard Project
**Type**: Web dashboard with API backend
**Current Version**: 1.0.0
## Project Description
This is a sample project created to demonstrate the Workspace development suite capabilities including:
- Feature-centric development methodology
- Real-time project dashboard
- Comprehensive API endpoints
- Database-driven project management
## Current Status
**Development Phase**: Sample Data Demonstration
**Test Status**: โ
Sample data populated
**Build Status**: โ
Ready for development
## Key Features Working
- โ
Project management dashboard
- โ
Feature tracking and status monitoring
- โ
Task management with state transitions
- โ
Real-time API endpoints
- โ
Database-backed storage
## Success Criteria
### Core Functionality
- โ
Dashboard displays project metrics
- โ
API endpoints return sample data
- โ
Feature state management working
- โ
Task tracking operational
### Quality Metrics
- โ
All API endpoints responding
- โ
Database queries optimized
- โ
Sample data representative of real usage
## Next Steps
Use this sample project to:
1. Test dashboard functionality
2. Validate API endpoints
3. Experiment with feature management
4. Learn the development methodology
---
*Created by wsb sample command*"#;
std::fs::write("CLAUDE.md", claude_content)?;
println!(" {} Created CLAUDE.md", "โ
".green());
// Create package.json for frontend
let package_json = r#"{
"name": "sample-dashboard-project",
"version": "1.0.0",
"description": "Sample project for Workspace development suite",
"main": "index.js",
"scripts": {
"dev": "wsb mcp-server",
"test": "wsb status --include-features --include-metrics"
},
"keywords": ["workspace", "dashboard", "sample"],
"author": "Workspace Development Suite",
"license": "MIT"
}"#;
std::fs::write("package.json", package_json)?;
println!(" {} Created package.json", "โ
".green());
// Create README.md
let readme_content = r#"# Sample Dashboard Project
This is a sample project created by the Workspace development suite to demonstrate:
- Feature-centric development methodology
- Real-time project dashboard
- API-driven development workflow
## Quick Start
1. View project status: `wsb status --include-features`
2. Start dashboard: `wsb mcp-server`
3. Open browser: http://localhost:3000
## Commands
- `wsb sample --data` - Populate with more sample data
- `wsb feature list` - View all features
- `wsb task list` - View all tasks
- `wsb status --include-metrics` - View project metrics
This sample demonstrates real-world usage patterns and can be used as a template for new projects.
"#;
std::fs::write("README.md", readme_content)?;
println!(" {} Created README.md", "โ
".green());
println!("{} Sample project structure created", "โ
".green().bold());
Ok(())
}
fn populate_sample_data(force: bool) -> Result<()> {
println!("{} Populating database with sample data...", "๐๏ธ".blue().bold());
// Ensure database directory exists
let db_path = std::path::Path::new(".wsb/project.db");
std::fs::create_dir_all(db_path.parent().unwrap())?;
// Check if database exists and has data
if db_path.exists() && !force {
let output = std::process::Command::new("sqlite3")
.arg(&db_path)
.arg("SELECT COUNT(*) FROM features;")
.output()?;
if output.status.success() {
let count = String::from_utf8_lossy(&output.stdout).trim().parse::<i32>().unwrap_or(0);
if count > 0 {
println!("{} Database already has {} features (use --force to overwrite)", "โ ๏ธ".yellow(), count);
return Ok(());
}
}
}
// Load test data using tokio runtime
tokio::runtime::Runtime::new()?.block_on(async {
populate_sample_data_async(force).await
})
}
async fn populate_sample_data_async(force: bool) -> Result<()> {
let db_path = std::env::current_dir()?.join(".wsb").join("project.db");
// Initialize database if it doesn't exist
let pool = if db_path.exists() && !force {
sqlx::SqlitePool::connect(&format!("sqlite:{}", db_path.display())).await
.map_err(|e| anyhow::anyhow!("Failed to connect to database: {}", e))?
} else {
wsb::entities::database::initialize_database(&db_path).await?
};
let entity_manager = wsb::entities::EntityManager::new(pool);
// Load test data from test_data.sql if it exists
let test_data_path = std::path::Path::new("test_data.sql");
if test_data_path.exists() {
println!(" {} Loading comprehensive data from test_data.sql...", "๐ฅ".blue());
// Load the SQL file content
let test_data = std::fs::read_to_string(test_data_path)?;
// Use sqlite3 command to load the data to avoid UUID parsing issues
let db_path_str = db_path.to_string_lossy();
let output = std::process::Command::new("sqlite3")
.arg(&*db_path_str)
.stdin(std::process::Stdio::piped())
.stdout(std::process::Stdio::piped())
.stderr(std::process::Stdio::piped())
.spawn()?;
if let Some(stdin) = output.stdin.as_ref() {
use std::io::Write;
let mut stdin = stdin;
stdin.write_all(test_data.as_bytes())?;
}
let result = output.wait_with_output()?;
if !result.status.success() {
eprintln!("Warning: Some SQL statements may have failed");
}
println!(" {} Comprehensive test data loaded", "โ
".green());
} else {
// Create minimal sample data using EntityManager methods
println!(" {} Creating minimal sample data...", "๐ง".blue());
// Ensure we have a current project (creates one if needed)
let _project = entity_manager.get_current_project().await?;
// Create sample features with diverse states and categories
println!(" {} Creating features with diverse states...", "๐ฏ".blue());
// Generate extensive diverse features covering all states, categories, and priorities
let feature_states = vec!["NotImplemented", "Planned", "InProgress", "Implemented", "Tested", "Deprecated"];
let test_statuses = vec!["NotTested", "InProgress", "Passed", "Failed", "Skipped"];
let categories = vec!["Frontend", "Backend", "Database", "Security", "Performance", "Testing", "Documentation", "DevOps", "Analytics", "Mobile", "AI/ML", "Infrastructure", "UX/UI", "Integration", "Monitoring"];
let priorities = vec!["Low", "Medium", "High", "Critical"];
let feature_templates = vec![
("Dashboard", "web dashboard for project visualization and monitoring"),
("API Gateway", "centralized API gateway for microservices communication"),
("Authentication", "secure user authentication and authorization system"),
("Database Migration", "automated database schema migration and versioning"),
("Real-time Sync", "real-time data synchronization across multiple clients"),
("Cache Layer", "distributed caching layer for improved performance"),
("Monitoring", "comprehensive application monitoring and alerting"),
("Search Engine", "full-text search capabilities with advanced filtering"),
("File Upload", "secure file upload and processing system"),
("Notification Hub", "multi-channel notification delivery system"),
("User Management", "complete user lifecycle management portal"),
("Audit Trail", "comprehensive audit logging and compliance tracking"),
("Report Generator", "automated report generation and distribution"),
("Backup System", "automated backup and disaster recovery solution"),
("Load Balancer", "intelligent load balancing and traffic distribution"),
("Security Scanner", "automated security vulnerability scanning"),
("Performance Analytics", "detailed performance metrics and optimization insights"),
("Content Management", "flexible content management and publishing system"),
("Email Service", "reliable email delivery and template management"),
("Chat Integration", "real-time chat and collaboration features"),
("Payment Gateway", "secure payment processing and transaction management"),
("Inventory System", "comprehensive inventory tracking and management"),
("Workflow Engine", "flexible business process automation engine"),
("Data Pipeline", "scalable data processing and ETL pipeline"),
("Mobile App", "native mobile application with offline capabilities"),
("AI Recommendations", "machine learning powered recommendation engine"),
("Social Features", "social networking and community engagement tools"),
("Third-party Integration", "seamless integration with external services"),
("Analytics Dashboard", "advanced analytics and business intelligence dashboard"),
("Video Processing", "automated video transcoding and streaming service"),
("Geolocation Service", "location-based services and mapping integration"),
("Blockchain Integration", "decentralized ledger and smart contract support"),
("IoT Gateway", "Internet of Things device management and data collection"),
("Machine Learning", "automated machine learning model training and deployment"),
("GraphQL API", "flexible GraphQL API with real-time subscriptions"),
("Microservices", "containerized microservices architecture"),
("Edge Computing", "distributed edge computing and CDN integration"),
("Data Warehouse", "scalable data warehouse and business intelligence platform"),
("Compliance Engine", "automated regulatory compliance and reporting system"),
("Resource Scheduler", "intelligent resource allocation and scheduling system"),
];
let mut diverse_features = Vec::new();
for (i, (name, desc_suffix)) in feature_templates.iter().enumerate() {
let state = feature_states[i % feature_states.len()];
let test_status = test_statuses[i % test_statuses.len()];
let category = categories[i % categories.len()];
let priority = priorities[i % priorities.len()];
diverse_features.push((
format!("{} {}", name, if i > 0 { format!("v{}", (i / feature_templates.len()) + 1) } else { String::new() }).trim().to_string(),
format!("Implement {} with {} priority focus", desc_suffix, priority.to_lowercase()),
state,
test_status,
category,
priority
));
}
for (title, desc, _state, _test_status, _category, _priority) in diverse_features {
// Create basic features with proper arguments
let _project = entity_manager.get_current_project().await?.ok_or_else(|| anyhow::anyhow!("No active project"))?;
if let Err(e) = entity_manager.create_feature(title.clone(), desc.clone()).await {
eprintln!("Warning: Failed to create feature '{}': {}", title, e);
}
}
// Create sample tasks with diverse statuses and priorities
println!(" {} Creating tasks with diverse statuses...", "โ
".blue());
// Generate extensive diverse tasks covering all statuses, categories, and priorities
let task_statuses = vec!["Pending", "InProgress", "Completed", "Blocked", "Cancelled"];
let task_categories = vec!["feature", "bug", "maintenance", "research", "documentation", "testing", "deployment", "security", "performance", "refactoring", "integration", "monitoring", "infrastructure", "training", "compliance"];
let task_templates = vec![
("Setup", "initial project structure and configuration", "infrastructure"),
("Implement", "core functionality and business logic", "feature"),
("Design", "user interface and experience patterns", "feature"),
("Configure", "development and production environments", "infrastructure"),
("Optimize", "performance bottlenecks and resource usage", "performance"),
("Test", "comprehensive test coverage and quality assurance", "testing"),
("Document", "technical specifications and user guides", "documentation"),
("Deploy", "automated deployment and release processes", "deployment"),
("Monitor", "application health and performance metrics", "monitoring"),
("Secure", "vulnerability assessment and security hardening", "security"),
("Integrate", "third-party services and external APIs", "integration"),
("Refactor", "code quality and architectural improvements", "refactoring"),
("Research", "technology evaluation and proof of concepts", "research"),
("Train", "team knowledge transfer and skill development", "training"),
("Maintain", "system maintenance and technical debt reduction", "maintenance"),
("Fix", "critical bugs and production issues", "bug"),
("Validate", "compliance requirements and regulatory standards", "compliance"),
("Scale", "horizontal and vertical scaling capabilities", "performance"),
("Backup", "data protection and disaster recovery procedures", "infrastructure"),
("Upgrade", "dependency updates and version migrations", "maintenance"),
("Analyze", "user behavior and system performance patterns", "research"),
("Automate", "manual processes and workflow optimization", "infrastructure"),
("Review", "code quality and architectural decisions", "maintenance"),
("Plan", "feature roadmap and technical architecture", "research"),
("Coordinate", "cross-team collaboration and resource allocation", "feature"),
("Evaluate", "technology stack and tool effectiveness", "research"),
("Streamline", "development workflow and productivity tools", "infrastructure"),
("Enhance", "user experience and interface improvements", "feature"),
("Troubleshoot", "production issues and system debugging", "bug"),
("Standardize", "coding conventions and development practices", "maintenance"),
("Containerize", "application deployment and orchestration", "deployment"),
("Profile", "performance analysis and optimization opportunities", "performance"),
("Validate", "data integrity and business rule enforcement", "testing"),
("Migrate", "legacy system modernization and data transfer", "infrastructure"),
("Prototype", "experimental features and innovation projects", "research"),
("Benchmark", "performance comparison and competitive analysis", "performance"),
("Audit", "security assessment and compliance verification", "security"),
("Customize", "client-specific requirements and configurations", "feature"),
("Synchronize", "data consistency across distributed systems", "integration"),
("Visualize", "data presentation and dashboard development", "feature"),
];
let mut diverse_tasks = Vec::new();
for (i, (action, desc_suffix, default_category)) in task_templates.iter().enumerate() {
let status = task_statuses[i % task_statuses.len()];
let priority = priorities[i % priorities.len()];
let category = if i < task_categories.len() { task_categories[i] } else { default_category };
diverse_tasks.push((
format!("{} {} - Phase {}", action, desc_suffix, (i / 10) + 1),
format!("{} {} with {} priority and {} status tracking", action, desc_suffix, priority.to_lowercase(), status.to_lowercase()),
status,
priority,
category
));
}
for (title, desc, _status, _priority, _category) in diverse_tasks {
let project = entity_manager.get_current_project().await?.ok_or_else(|| anyhow::anyhow!("No active project"))?;
// Need feature_id - use first feature for simplicity
let features = entity_manager.list_features_by_project(&project.id).await?;
if let Some(_feature) = features.first() {
if let Err(e) = entity_manager.create_task(title.clone(), desc.clone()).await {
eprintln!("Warning: Failed to create task '{}': {}", title, e);
}
}
}
println!(" {} Comprehensive sample data created with {} diverse categories", "โ
".green(), categories.len());
}
// Show summary using entity manager methods
let project = entity_manager.get_current_project().await?.ok_or_else(|| anyhow::anyhow!("No active project"))?;
let features = entity_manager.list_features_by_project(&project.id).await?;
let tasks = entity_manager.list_tasks_by_project(&project.id, None).await?;
println!(" {} {} features created", "๐".cyan(), features.len());
println!(" {} {} tasks created", "โ
".cyan(), tasks.len());
Ok(())
}
fn run_start_command(
continue_from: Option<String>,
debug_mode: bool,
project_setup: bool,
first_task: Option<String>,
) -> Result<()> {
if debug_mode {
println!("{}", "=== Start Command Debug Mode ===".bold().blue());
}
// Phase 1: Project Setup (if requested)
if project_setup {
return setup_new_project(first_task);
}
// Phase 2: Core Project Context Loading
let project_context = load_project_context(debug_mode)?;
// Phase 3: State Validation
validate_project_state(&project_context, debug_mode)?;
// Phase 4: Session Initialization
initialize_session(&project_context, continue_from, first_task, debug_mode)?;
Ok(())
}
fn setup_new_project(first_task: Option<String>) -> Result<()> {
println!("{}", "Setting up new project with feature-centric methodology...".bold().green());
// Create project structure
let internal_dir = std::path::Path::new("internal");
std::fs::create_dir_all(internal_dir)?;
let ws_dir = std::path::Path::new(".wsb");
std::fs::create_dir_all(ws_dir)?;
// Get project name once
let project_name = std::env::current_dir()?
.file_name()
.and_then(|n| n.to_str())
.map(|s| s.to_string())
.unwrap_or_else(|| "New Project".to_string());
// Create CLAUDE.md if it doesn't exist
let claude_md = std::path::Path::new("CLAUDE.md");
if !claude_md.exists() {
let claude_content = format!(
"# {}\n\n## Project Overview\n\n[Brief project description]\n\n## Current Status\n\n๐ **Project Initialization Phase**\n- Setting up feature-centric development methodology\n- Establishing persistent knowledge management\n\n## Key Achievements\n\n- โ
Project repository initialized\n- โ
Feature-centric framework established\n\n## Current Focus\n\nSetting up foundational project features and development methodology.\n\n## Success Criteria\n\n- [ ] Complete project feature inventory\n- [ ] Implement core functionality features\n- [ ] Establish testing methodology\n- [ ] Achieve target feature coverage\n\n## Next Steps\n\nRefer to internal/features.md for current priorities and feature status.\n",
project_name
);
std::fs::write(claude_md, claude_content)?;
println!("Created CLAUDE.md project brain");
}
// Create initial features.md
let features_md = internal_dir.join("features.md");
if !features_md.exists() {
let features_content = format!(
"# {} Features - COMPLETE INVENTORY\n\n**Date**: {}\n**Purpose**: Central repository for ALL project features and development state\n**Goal**: Achieve 100% feature implementations with complete test coverage\n**Current Status**: 0 total features tracked\n**Next Priority**: F0001 - Project Foundation\n\n## CURRENT PROJECT SCORES\n**Total Features**: 0\n**Implementation Score**: 0/0 = 0% implemented\n**Test Coverage Score**: 0/0 = 0% tested\n**Quality Score**: 0/0 features with passing tests = 0% validated\n\n## Project Foundation\n\n| ID | Feature | Description | State | Notes |\n|---|---|---|---|---|\n| F0001 | **Project Initialization** | Basic project structure and tooling setup | โ | Starting point for development |\n\n---\n\n*This feature inventory will be populated as development progresses.*\n",
project_name,
chrono::Utc::now().format("%Y-%m-%d")
);
std::fs::write(&features_md, features_content)?;
println!("Created initial features.md");
}
// Setup Git exclusions
setup_git_exclusions()?;
println!("{}", "Project setup completed successfully!".bold().green());
if let Some(task) = first_task {
println!("Ready to start with: {}", task.bold());
}
Ok(())
}
fn load_project_context(debug_mode: bool) -> Result<ProjectContext> {
if debug_mode {
println!("Loading project context...");
}
let project_root = get_project_root()?;
let workspace_state = WorkspaceState::load(&project_root)?;
// Load CLAUDE.md
let claude_md_path = project_root.join("CLAUDE.md");
let claude_content = if claude_md_path.exists() {
std::fs::read_to_string(&claude_md_path)?
} else {
String::new()
};
// Load features.md
let features_md_path = project_root.join("internal").join("features.md");
let features_content = if features_md_path.exists() {
std::fs::read_to_string(&features_md_path)?
} else {
String::new()
};
// Load directives.md
let directives_md_path = project_root.join("internal").join("directives.md");
let directives_content = if directives_md_path.exists() {
std::fs::read_to_string(&directives_md_path)?
} else {
String::new()
};
Ok(ProjectContext {
project_root,
workspace_state,
claude_content,
features_content,
directives_content,
})
}
fn validate_project_state(context: &ProjectContext, debug_mode: bool) -> Result<()> {
if debug_mode {
println!("Validating project state...");
}
// Check if codebase compiles
let compile_result = Command::new("cargo")
.arg("check")
.arg("--quiet")
.current_dir(&context.project_root)
.output();
match compile_result {
Ok(output) if output.status.success() => {
if debug_mode {
println!("โ
Code compilation check passed");
}
}
Ok(_) => {
println!("{}", "โ ๏ธ Compilation issues detected".yellow());
}
Err(_) => {
if debug_mode {
println!("โน๏ธ Cargo not available or not a Rust project");
}
}
}
// Validate Git exclusions
validate_git_exclusions(debug_mode)?;
Ok(())
}
fn initialize_session(
context: &ProjectContext,
continue_from: Option<String>,
first_task: Option<String>,
debug_mode: bool,
) -> Result<()> {
if debug_mode {
println!("Initializing development session...");
}
// Parse features.md to get current status
let (total_features, implemented_features) = parse_feature_stats(&context.features_content);
println!("{}", "Session Initialized Successfully".bold().green());
println!();
// Project overview
let project_name = context.workspace_state.project_name
.as_deref()
.unwrap_or("Unknown Project");
println!("{}: {}", "Project".bold(), project_name);
if !context.claude_content.is_empty() {
if let Some(current_status) = extract_current_status(&context.claude_content) {
println!("{}: {}", "Current Status".bold(), current_status);
}
}
// Feature analysis
if total_features > 0 {
let implementation_rate = (implemented_features as f64 / total_features as f64 * 100.0) as u32;
println!();
println!("{}", "### Features.md Analysis".bold());
println!("{}: {} ({}% implemented)", "Total Features".bold(), total_features, implementation_rate);
if let Some(next_priority) = extract_next_priority(&context.features_content) {
println!("{}: {}", "Next Priority".bold(), next_priority);
}
}
// Critical rules
if !context.directives_content.is_empty() {
println!();
println!("{}", "### Critical Rules Loaded".bold());
println!("- Defensive security only - no malicious code creation");
println!("- File creation only when explicitly required");
println!("- Feature-centric development: All work organized around features.md");
println!("- Test integration: Automatic feature state updates");
}
// Starting point
println!();
println!("{}", "### Immediate Next Action".bold());
if let Some(task) = first_task {
println!("{}: {}", "Starting Task".bold(), task);
} else if let Some(continue_task) = continue_from {
println!("{}: Continue from {}", "Resuming".bold(), continue_task);
} else if let Some(next_priority) = extract_next_priority(&context.features_content) {
println!("{}: {}", "Next Priority".bold(), next_priority);
} else {
println!("{}: Ready for feature development", "Status".bold());
}
Ok(())
}
fn setup_git_exclusions() -> Result<()> {
let git_dir = std::path::Path::new(".git");
if git_dir.exists() {
let exclude_file = git_dir.join("info").join("exclude");
std::fs::create_dir_all(exclude_file.parent().unwrap())?;
let mut exclude_content = String::new();
if exclude_file.exists() {
exclude_content = std::fs::read_to_string(&exclude_file)?;
}
let entries_to_add = vec!["CLAUDE.md", "internal/", ".claude/"];
let mut modified = false;
for entry in entries_to_add {
if !exclude_content.contains(entry) {
if !exclude_content.ends_with('\n') && !exclude_content.is_empty() {
exclude_content.push('\n');
}
exclude_content.push_str(entry);
exclude_content.push('\n');
modified = true;
}
}
if modified {
std::fs::write(&exclude_file, exclude_content)?;
println!("Updated Git exclusions for project files");
}
}
Ok(())
}
fn validate_git_exclusions(debug_mode: bool) -> Result<()> {
let git_dir = std::path::Path::new(".git");
if !git_dir.exists() {
if debug_mode {
println!("โน๏ธ Not a Git repository");
}
return Ok(());
}
let exclude_file = git_dir.join("info").join("exclude");
if !exclude_file.exists() {
if debug_mode {
println!("โ ๏ธ Git exclude file not found");
}
return Ok(());
}
let exclude_content = std::fs::read_to_string(&exclude_file)?;
let required_entries = vec!["CLAUDE.md", "internal/", ".claude/"];
for entry in required_entries {
if !exclude_content.contains(entry) {
println!("{}: {} not in Git exclusions", "Warning".yellow(), entry);
} else if debug_mode {
println!("โ
{} properly excluded from Git", entry);
}
}
Ok(())
}
fn parse_feature_stats(features_content: &str) -> (u32, u32) {
let mut total = 0;
let mut implemented = 0;
for line in features_content.lines() {
// Match actual feature table rows: | F#### | **Name** | Description | State | Notes |
if line.starts_with("| F") && line.matches("|").count() >= 5 {
total += 1;
if line.contains("๐ข") {
implemented += 1;
}
}
}
(total, implemented)
}
fn extract_current_status(claude_content: &str) -> Option<String> {
for line in claude_content.lines() {
if line.starts_with("**Development Phase**:") {
return Some(line.trim_start_matches("**Development Phase**: ").to_string());
}
}
None
}
fn extract_next_priority(features_content: &str) -> Option<String> {
for line in features_content.lines() {
if line.starts_with("**Next Priority**:") {
return Some(line.trim_start_matches("**Next Priority**: ").to_string());
}
}
None
}
#[derive(Debug)]
struct ProjectContext {
project_root: PathBuf,
workspace_state: WorkspaceState,
claude_content: String,
features_content: String,
directives_content: String,
}
fn run_end_command(
summary: Option<String>,
debug_mode: bool,
force: bool,
skip_docs: bool,
) -> Result<()> {
if debug_mode {
println!("{}", "=== End Command Debug Mode ===".bold().blue());
}
// Phase 1: Load current project context
let project_context = load_project_context(debug_mode)?;
// Phase 2: Session accuracy validation (unless forced)
if !force {
validate_session_accuracy(&project_context, debug_mode)?;
}
// Phase 3: Documentation consolidation (unless skipped)
if !skip_docs {
consolidate_session_documentation(&project_context, summary.as_deref(), debug_mode)?;
}
// Phase 4: Feature state updates
update_feature_states(&project_context, debug_mode)?;
// Phase 5: Session completion
finalize_session(&project_context, summary.as_deref(), debug_mode)?;
Ok(())
}
fn validate_session_accuracy(context: &ProjectContext, debug_mode: bool) -> Result<()> {
if debug_mode {
println!("Validating session accuracy...");
}
// Check compilation status
let compile_result = Command::new("cargo")
.arg("check")
.arg("--quiet")
.current_dir(&context.project_root)
.output();
match compile_result {
Ok(output) if output.status.success() => {
if debug_mode {
println!("โ
Code compilation successful");
}
}
Ok(output) => {
println!("{}", "โ ๏ธ Compilation issues detected:".yellow());
println!("{}", String::from_utf8_lossy(&output.stderr));
return Err(anyhow::anyhow!("Session ending with compilation issues. Use --force to override."));
}
Err(_) => {
if debug_mode {
println!("โน๏ธ Cargo not available or not a Rust project");
}
}
}
// Check test status
let test_result = Command::new("cargo")
.arg("test")
.arg("--quiet")
.current_dir(&context.project_root)
.output();
match test_result {
Ok(output) if output.status.success() => {
if debug_mode {
println!("โ
All tests passing");
}
}
Ok(output) => {
println!("{}", "โ ๏ธ Test failures detected:".yellow());
println!("{}", String::from_utf8_lossy(&output.stderr));
return Err(anyhow::anyhow!("Session ending with test failures. Use --force to override."));
}
Err(_) => {
if debug_mode {
println!("โน๏ธ Test suite not available");
}
}
}
Ok(())
}
fn consolidate_session_documentation(
context: &ProjectContext,
summary: Option<&str>,
debug_mode: bool,
) -> Result<()> {
if debug_mode {
println!("Consolidating session documentation...");
}
let now = chrono::Utc::now();
let date_str = now.format("%Y-%m-%d").to_string();
// Generate session summary
let session_summary = if let Some(provided_summary) = summary {
provided_summary.to_string()
} else {
generate_automatic_session_summary(context)?
};
// Update CLAUDE.md with session results
update_claude_md_with_session(context, &session_summary, &date_str, debug_mode)?;
// Update progress tracking
update_progress_tracking(context, &session_summary, &date_str, debug_mode)?;
Ok(())
}
fn generate_automatic_session_summary(context: &ProjectContext) -> Result<String> {
// Parse current feature stats
let (total_features, implemented_features) = parse_feature_stats(&context.features_content);
let implementation_rate = if total_features > 0 {
(implemented_features as f64 / total_features as f64 * 100.0) as u32
} else {
0
};
// Check for recent changes (simplified check)
let git_status = Command::new("git")
.args(&["status", "--porcelain"])
.current_dir(&context.project_root)
.output();
let changes_made = git_status
.map(|output| !output.stdout.is_empty())
.unwrap_or(false);
let summary = format!(
"Development session completed. Project at {}% implementation ({}/{} features). {}",
implementation_rate,
implemented_features,
total_features,
if changes_made { "Changes made to codebase." } else { "No changes made." }
);
Ok(summary)
}
fn update_claude_md_with_session(
context: &ProjectContext,
session_summary: &str,
date: &str,
debug_mode: bool,
) -> Result<()> {
if debug_mode {
println!("Updating CLAUDE.md with session results...");
}
let claude_md_path = context.project_root.join("CLAUDE.md");
if !claude_md_path.exists() {
return Ok(()); // Skip if no CLAUDE.md
}
let mut content = context.claude_content.clone();
// Update last session date
if let Some(pos) = content.find("**Last Session**:") {
let line_end = content[pos..].find('\n').unwrap_or(0);
let old_line = &content[pos..pos + line_end];
let new_line = format!("**Last Session**: {}", date);
content = content.replace(old_line, &new_line);
}
// Add session summary to recent sessions if not already there
if !content.contains(&format!("### Session {}", date)) {
let session_entry = format!(
"\n### Session {} Summary\n\n**Achievement**: {}\n",
date, session_summary
);
// Insert before the "## Previous Session Summary" section if it exists
if let Some(pos) = content.find("## Previous Session Summary") {
content.insert_str(pos, &session_entry);
} else {
// Add at the end
content.push_str(&session_entry);
}
}
std::fs::write(&claude_md_path, content)?;
if debug_mode {
println!("โ
Updated CLAUDE.md with session summary");
}
Ok(())
}
fn update_progress_tracking(
context: &ProjectContext,
session_summary: &str,
date: &str,
debug_mode: bool,
) -> Result<()> {
if debug_mode {
println!("Updating progress tracking...");
}
let progress_md_path = context.project_root.join("internal").join("progress_tracking.md");
if !progress_md_path.exists() {
return Ok(()); // Skip if no progress tracking file
}
let mut content = std::fs::read_to_string(&progress_md_path)?;
// Add new session entry at the top of the session history
let session_entry = format!(
"### Session {} Summary\n\n**Achievement**: {}\n**Status**: Session completed successfully\n\n",
date, session_summary
);
// Insert after "## Session History" line
if let Some(pos) = content.find("## Session History\n") {
let insert_pos = pos + "## Session History\n".len();
content.insert_str(insert_pos, &format!("\n{}", session_entry));
} else {
// Add at the beginning if no session history section
content = format!("## Session History\n\n{}{}", session_entry, content);
}
std::fs::write(&progress_md_path, content)?;
if debug_mode {
println!("โ
Updated progress tracking");
}
Ok(())
}
fn update_feature_states(_context: &ProjectContext, debug_mode: bool) -> Result<()> {
log::debug!("Updating feature states (debug mode: {})", debug_mode);
if debug_mode {
println!("Updating feature states...");
}
// For now, this is a placeholder. In a full implementation, this would:
// 1. Scan for completed features based on code changes
// 2. Update test status based on test results
// 3. Validate feature state transitions
// 4. Update features.md with new states
if debug_mode {
println!("โน๏ธ Feature state updates not yet implemented");
}
Ok(())
}
fn finalize_session(
context: &ProjectContext,
summary: Option<&str>,
debug_mode: bool,
) -> Result<()> {
if debug_mode {
println!("Finalizing session...");
}
// Parse current feature stats for final report
let (total_features, implemented_features) = parse_feature_stats(&context.features_content);
let implementation_rate = if total_features > 0 {
(implemented_features as f64 / total_features as f64 * 100.0) as u32
} else {
0
};
println!("{}", "Session Ended Successfully".bold().green());
println!();
// Project status
let project_name = context.workspace_state.project_name
.as_deref()
.unwrap_or("Unknown Project");
println!("{}: {}", "Project".bold(), project_name);
// Session summary
if let Some(summary_text) = summary {
println!("{}: {}", "Summary".bold(), summary_text);
}
// Final feature status
println!();
println!("{}", "### Final Project Status".bold());
println!("{}: {} ({}% implemented)", "Total Features".bold(), total_features, implementation_rate);
println!("{}: {}", "Features Completed".bold(), implemented_features);
// Next session preparation
if let Some(next_priority) = extract_next_priority(&context.features_content) {
println!();
println!("{}", "### Next Session Preparation".bold());
println!("{}: {}", "Next Priority".bold(), next_priority);
}
println!();
println!("{}", "Documentation updated. Ready for next session.".bold().blue());
Ok(())
}
fn run_consolidate_command(
debug_mode: bool,
force: bool,
generate_diagrams: bool,
preserve_complexity: bool,
) -> Result<()> {
if debug_mode {
println!("{}", "=== Consolidate Command Debug Mode ===".bold().blue());
}
// Phase 1: Load current project context
let project_context = load_project_context(debug_mode)?;
// Phase 2: Validate documentation state (unless forced)
if !force {
validate_documentation_state(&project_context, debug_mode)?;
}
// Phase 3: Analyze documentation complexity
let complexity_analysis = analyze_documentation_complexity(&project_context, debug_mode)?;
// Phase 4: Consolidate documentation
consolidate_documentation(&project_context, &complexity_analysis, preserve_complexity, debug_mode)?;
// Phase 5: Generate architectural diagrams (if requested)
if generate_diagrams {
generate_architectural_diagrams(&project_context, debug_mode)?;
}
// Phase 6: Finalize consolidation
finalize_consolidation(&project_context, &complexity_analysis, debug_mode)?;
Ok(())
}
fn validate_documentation_state(context: &ProjectContext, debug_mode: bool) -> Result<()> {
if debug_mode {
println!("Validating documentation state...");
}
// Check for required documentation files
let required_files = vec![
"CLAUDE.md",
"internal/features.md",
"internal/progress_tracking.md",
"internal/directives.md"
];
for file in required_files {
let file_path = context.project_root.join(file);
if !file_path.exists() {
return Err(anyhow::anyhow!("Required documentation file missing: {}. Use --force to override.", file));
}
if debug_mode {
println!("โ
Found {}", file);
}
}
// Check for documentation bloat (files over certain size)
let claude_md_path = context.project_root.join("CLAUDE.md");
if let Ok(metadata) = std::fs::metadata(&claude_md_path) {
let size_kb = metadata.len() / 1024;
if size_kb > 100 { // Over 100KB
println!("{}", format!("โ ๏ธ CLAUDE.md is large ({}KB) - consolidation recommended", size_kb).yellow());
} else if debug_mode {
println!("โ
CLAUDE.md size acceptable ({}KB)", size_kb);
}
}
Ok(())
}
#[derive(Debug)]
struct ComplexityAnalysis {
claude_md_sections: usize,
progress_sessions: usize,
features_total: usize,
directives_total: usize,
architectural_decisions: usize,
requires_consolidation: bool,
}
fn analyze_documentation_complexity(context: &ProjectContext, debug_mode: bool) -> Result<ComplexityAnalysis> {
if debug_mode {
println!("Analyzing documentation complexity...");
}
// Analyze CLAUDE.md sections
let claude_sections = context.claude_content.matches("##").count();
// Analyze progress tracking sessions
let progress_path = context.project_root.join("internal").join("progress_tracking.md");
let progress_content = if progress_path.exists() {
std::fs::read_to_string(&progress_path)?
} else {
String::new()
};
let progress_sessions = progress_content.matches("### Session").count();
// Analyze features
let (features_total, _) = parse_feature_stats(&context.features_content);
// Analyze directives
let directives_total = context.directives_content.matches("###").count();
// Check for architectural decisions
let arch_decisions_path = context.project_root.join("internal").join("architectural_decisions.md");
let arch_decisions_content = if arch_decisions_path.exists() {
std::fs::read_to_string(&arch_decisions_path)?
} else {
String::new()
};
let architectural_decisions = arch_decisions_content.matches("##").count();
// Determine if consolidation is needed
let requires_consolidation = claude_sections > 20 || progress_sessions > 50 || features_total > 300;
let analysis = ComplexityAnalysis {
claude_md_sections: claude_sections,
progress_sessions,
features_total: features_total as usize,
directives_total,
architectural_decisions,
requires_consolidation,
};
if debug_mode {
println!("๐ Complexity Analysis:");
println!(" - CLAUDE.md sections: {}", analysis.claude_md_sections);
println!(" - Progress sessions: {}", analysis.progress_sessions);
println!(" - Total features: {}", analysis.features_total);
println!(" - Directives: {}", analysis.directives_total);
println!(" - Architectural decisions: {}", analysis.architectural_decisions);
println!(" - Requires consolidation: {}", analysis.requires_consolidation);
}
Ok(analysis)
}
fn consolidate_documentation(
context: &ProjectContext,
analysis: &ComplexityAnalysis,
preserve_complexity: bool,
debug_mode: bool,
) -> Result<()> {
if debug_mode {
println!("Consolidating documentation...");
}
if !analysis.requires_consolidation && !preserve_complexity {
if debug_mode {
println!("โน๏ธ Documentation within acceptable limits, no consolidation needed");
}
return Ok(());
}
// Create backup before consolidation
create_documentation_backup(context, debug_mode)?;
// Consolidate CLAUDE.md if it's getting large
if analysis.claude_md_sections > 15 {
consolidate_claude_md(context, preserve_complexity, debug_mode)?;
}
// Archive old progress sessions
if analysis.progress_sessions > 30 {
archive_old_progress_sessions(context, preserve_complexity, debug_mode)?;
}
// Consolidate features if getting unwieldy
if analysis.features_total > 200 {
consolidate_features_documentation(context, preserve_complexity, debug_mode)?;
}
Ok(())
}
fn create_documentation_backup(context: &ProjectContext, debug_mode: bool) -> Result<()> {
if debug_mode {
println!("Creating documentation backup...");
}
let backup_dir = context.project_root.join("internal").join("backups");
std::fs::create_dir_all(&backup_dir)?;
let timestamp = chrono::Utc::now().format("%Y%m%d_%H%M%S");
let backup_subdir = backup_dir.join(format!("consolidation_{}", timestamp));
std::fs::create_dir_all(&backup_subdir)?;
// Backup key files
let files_to_backup = vec![
"CLAUDE.md",
"internal/features.md",
"internal/progress_tracking.md",
"internal/directives.md",
];
for file in files_to_backup {
let source = context.project_root.join(file);
if source.exists() {
let dest = backup_subdir.join(file.replace("/", "_"));
std::fs::copy(&source, &dest)?;
if debug_mode {
println!(" โ
Backed up {}", file);
}
}
}
println!("๐ฆ Documentation backup created: {}", backup_subdir.display());
Ok(())
}
fn consolidate_claude_md(context: &ProjectContext, preserve_complexity: bool, debug_mode: bool) -> Result<()> {
if debug_mode {
println!("Consolidating CLAUDE.md...");
}
let mut content = context.claude_content.clone();
// Move old session summaries to archived section
if content.contains("## Previous Session Summary") && !preserve_complexity {
// Find and extract old sessions
let mut archived_sessions = String::new();
let sessions: Vec<&str> = content.split("### Session").collect();
if sessions.len() > 5 { // Keep only 5 most recent sessions
for session in &sessions[..sessions.len() - 5] {
if !session.trim().is_empty() {
archived_sessions.push_str(&format!("### Session{}", session));
}
}
// Keep only recent sessions in main content
let recent_sessions: String = sessions[sessions.len() - 5..]
.iter()
.enumerate()
.map(|(i, s)| if i == 0 { s.to_string() } else { format!("### Session{}", s) })
.collect();
content = recent_sessions;
// Write archived sessions to separate file
if !archived_sessions.is_empty() {
let archive_path = context.project_root.join("internal").join("archived_sessions.md");
let archive_content = format!("# Archived Session History\n\n{}", archived_sessions);
std::fs::write(&archive_path, archive_content)?;
if debug_mode {
println!(" ๐ Archived old sessions to internal/archived_sessions.md");
}
}
}
}
// Update main CLAUDE.md
std::fs::write(&context.project_root.join("CLAUDE.md"), content)?;
if debug_mode {
println!(" โ
CLAUDE.md consolidated");
}
Ok(())
}
fn archive_old_progress_sessions(context: &ProjectContext, preserve_complexity: bool, debug_mode: bool) -> Result<()> {
if debug_mode {
println!("Archiving old progress sessions...");
}
let progress_path = context.project_root.join("internal").join("progress_tracking.md");
if !progress_path.exists() {
return Ok(());
}
let content = std::fs::read_to_string(&progress_path)?;
if !preserve_complexity {
let sessions: Vec<&str> = content.split("### Session").collect();
if sessions.len() > 20 { // Keep only 20 most recent sessions
// Keep recent sessions
let recent_content: String = sessions[sessions.len() - 20..]
.iter()
.enumerate()
.map(|(i, s)| if i == 0 { s.to_string() } else { format!("### Session{}", s) })
.collect();
// Archive old sessions
let old_sessions: String = sessions[..sessions.len() - 20]
.iter()
.enumerate()
.map(|(i, s)| if i == 0 { s.to_string() } else { format!("### Session{}", s) })
.collect();
// Write updated progress tracking
std::fs::write(&progress_path, recent_content)?;
// Write archived sessions
if !old_sessions.trim().is_empty() {
let archive_path = context.project_root.join("internal").join("archived_progress.md");
let archive_content = format!("# Archived Progress Tracking\n\n{}", old_sessions);
std::fs::write(&archive_path, archive_content)?;
if debug_mode {
println!(" ๐ Archived old progress to internal/archived_progress.md");
}
}
}
}
if debug_mode {
println!(" โ
Progress sessions consolidated");
}
Ok(())
}
fn consolidate_features_documentation(_context: &ProjectContext, preserve_complexity: bool, debug_mode: bool) -> Result<()> {
if debug_mode {
println!("Consolidating features documentation...");
}
if preserve_complexity {
if debug_mode {
println!(" โน๏ธ Preserving feature complexity as requested");
}
return Ok(());
}
// For now, this is a placeholder for feature consolidation
// In a full implementation, this could:
// 1. Group completed features by category
// 2. Create summary tables for large feature sets
// 3. Archive very old completed features
if debug_mode {
println!(" โน๏ธ Feature consolidation not yet implemented");
}
Ok(())
}
fn generate_architectural_diagrams(context: &ProjectContext, debug_mode: bool) -> Result<()> {
if debug_mode {
println!("Generating architectural diagrams...");
}
let diagrams_dir = context.project_root.join("internal").join("diagrams");
std::fs::create_dir_all(&diagrams_dir)?;
// Generate feature dependency diagram
generate_feature_dependency_diagram(context, &diagrams_dir, debug_mode)?;
// Generate system architecture diagram
generate_system_architecture_diagram(context, &diagrams_dir, debug_mode)?;
Ok(())
}
fn generate_feature_dependency_diagram(context: &ProjectContext, diagrams_dir: &std::path::Path, debug_mode: bool) -> Result<()> {
if debug_mode {
println!(" Generating feature dependency diagram...");
}
let dot_content = format!(r#"
digraph feature_dependencies {{
rankdir=TB;
node [shape=box, style=filled];
// Parse features from features.md and generate DOT content
// This is a simplified example
subgraph cluster_core {{
label="Core Tool Features";
color=blue;
F0001 [label="Unified CLI", fillcolor=lightgreen];
F0002 [label="Shell Completion", fillcolor=lightgreen];
}}
subgraph cluster_commands {{
label="Command System";
color=red;
F0100 [label="Start Command", fillcolor=lightgreen];
F0101 [label="End Command", fillcolor=lightgreen];
F0102 [label="Consolidate Command", fillcolor=yellow];
}}
// Dependencies
F0100 -> F0001;
F0101 -> F0001;
F0102 -> F0001;
F0102 -> F0100;
F0102 -> F0101;
// Project: {}
// Generated: {}
}}
"#,
context.workspace_state.project_name.as_deref().unwrap_or("Unknown"),
chrono::Utc::now().format("%Y-%m-%d %H:%M:%S UTC")
);
let diagram_path = diagrams_dir.join("feature_dependencies.dot");
std::fs::write(&diagram_path, dot_content)?;
if debug_mode {
println!(" โ
Created {}", diagram_path.display());
}
Ok(())
}
fn generate_system_architecture_diagram(context: &ProjectContext, diagrams_dir: &std::path::Path, debug_mode: bool) -> Result<()> {
if debug_mode {
println!(" Generating system architecture diagram...");
}
let dot_content = format!(r#"
digraph system_architecture {{
rankdir=TB;
node [shape=box, style=filled];
subgraph cluster_cli {{
label="CLI Layer";
color=blue;
CLI [label="wsb Binary", fillcolor=lightblue];
Commands [label="Command Router", fillcolor=lightblue];
}}
subgraph cluster_tools {{
label="Tool Implementations";
color=green;
Refac [label="refac", fillcolor=lightgreen];
Scrap [label="scrap/unscrap", fillcolor=lightgreen];
St8 [label="st8", fillcolor=lightgreen];
Ldiff [label="ldiff", fillcolor=lightgreen];
}}
subgraph cluster_management {{
label="Project Management";
color=red;
Start [label="Start Command", fillcolor=lightyellow];
End [label="End Command", fillcolor=lightyellow];
Consolidate [label="Consolidate Command", fillcolor=lightyellow];
MCP [label="MCP Server", fillcolor=lightyellow];
}}
subgraph cluster_data {{
label="Data Layer";
color=purple;
Database [label="SQLite Database", fillcolor=lightpink];
Files [label="Internal Files", fillcolor=lightpink];
State [label="Workspace State", fillcolor=lightpink];
}}
// Connections
CLI -> Commands;
Commands -> Refac;
Commands -> Scrap;
Commands -> St8;
Commands -> Ldiff;
Commands -> Start;
Commands -> End;
Commands -> Consolidate;
Commands -> MCP;
Start -> State;
End -> Files;
Consolidate -> Files;
MCP -> Database;
// Project: {}
// Generated: {}
}}
"#,
context.workspace_state.project_name.as_deref().unwrap_or("Unknown"),
chrono::Utc::now().format("%Y-%m-%d %H:%M:%S UTC")
);
let diagram_path = diagrams_dir.join("system_architecture.dot");
std::fs::write(&diagram_path, dot_content)?;
if debug_mode {
println!(" โ
Created {}", diagram_path.display());
}
Ok(())
}
fn finalize_consolidation(context: &ProjectContext, analysis: &ComplexityAnalysis, debug_mode: bool) -> Result<()> {
if debug_mode {
println!("Finalizing consolidation...");
}
println!("{}", "Documentation Consolidation Complete".bold().green());
println!();
// Project overview
let project_name = context.workspace_state.project_name
.as_deref()
.unwrap_or("Unknown Project");
println!("{}: {}", "Project".bold(), project_name);
// Consolidation results
println!();
println!("{}", "### Consolidation Results".bold());
println!("{}: {}", "CLAUDE.md sections".bold(), analysis.claude_md_sections);
println!("{}: {}", "Progress sessions".bold(), analysis.progress_sessions);
println!("{}: {}", "Total features".bold(), analysis.features_total);
println!("{}: {}", "Directives".bold(), analysis.directives_total);
if analysis.requires_consolidation {
println!("{}", "โ
Documentation consolidated successfully".green());
} else {
println!("{}", "โน๏ธ Documentation within acceptable complexity limits".blue());
}
// Check if diagrams were generated
let diagrams_dir = context.project_root.join("internal").join("diagrams");
if diagrams_dir.exists() {
println!();
println!("{}", "### Generated Artifacts".bold());
if diagrams_dir.join("feature_dependencies.dot").exists() {
println!("๐ Feature dependency diagram: internal/diagrams/feature_dependencies.dot");
}
if diagrams_dir.join("system_architecture.dot").exists() {
println!("๐๏ธ System architecture diagram: internal/diagrams/system_architecture.dot");
}
println!("๐ก Use 'dot -Tpng filename.dot -o filename.png' to render diagrams");
}
println!();
println!("{}", "Documentation organization improved. Ready for continued development.".bold().blue());
Ok(())
}
fn run_status_command(
debug_mode: bool,
include_features: bool,
include_metrics: bool,
format: String,
) -> Result<()> {
if debug_mode {
println!("{}", "=== Status Command Debug Mode ===".bold().blue());
}
// Phase 1: Load current project context
let project_context = load_project_context(debug_mode)?;
// Phase 2: Calculate project metrics
let project_metrics = calculate_project_metrics(&project_context, debug_mode)?;
// Phase 3: Generate status report
match format.as_str() {
"json" => generate_json_status(&project_context, &project_metrics, include_features, include_metrics)?,
"summary" => generate_summary_status(&project_context, &project_metrics)?,
"human" | _ => generate_human_status(&project_context, &project_metrics, include_features, include_metrics, debug_mode)?,
}
Ok(())
}
#[derive(Debug)]
struct ProjectMetrics {
total_features: usize,
implemented_features: usize,
tested_features: usize,
implementation_rate: f64,
test_coverage_rate: f64,
features_by_state: std::collections::HashMap<String, usize>,
recent_activity: RecentActivity,
project_health: ProjectHealth,
}
#[derive(Debug)]
struct RecentActivity {
last_session_date: Option<String>,
sessions_this_week: usize,
features_completed_recently: usize,
git_commits_today: usize,
}
#[derive(Debug)]
struct ProjectHealth {
compilation_status: CompilationStatus,
test_status: TestStatus,
documentation_health: DocumentationHealth,
code_quality_score: f64,
}
#[derive(Debug)]
enum CompilationStatus {
Passing,
Failing(String),
Unknown,
}
#[derive(Debug)]
enum TestStatus {
#[allow(dead_code)]
AllPassing(usize),
#[allow(dead_code)]
SomeFailures(usize, usize),
Unknown,
}
#[derive(Debug)]
struct DocumentationHealth {
claude_md_size_kb: usize,
features_documented: bool,
progress_tracking_current: bool,
directives_present: bool,
}
fn calculate_project_metrics(context: &ProjectContext, debug_mode: bool) -> Result<ProjectMetrics> {
if debug_mode {
println!("Calculating project metrics...");
}
// Parse feature statistics
let (total_features, implemented_features) = parse_feature_stats(&context.features_content);
let tested_features = count_tested_features(&context.features_content);
let implementation_rate = if total_features > 0 {
implemented_features as f64 / total_features as f64 * 100.0
} else {
0.0
};
let test_coverage_rate = if total_features > 0 {
tested_features as f64 / total_features as f64 * 100.0
} else {
0.0
};
// Calculate features by state
let features_by_state = calculate_features_by_state(&context.features_content);
// Calculate recent activity
let recent_activity = calculate_recent_activity(context, debug_mode)?;
// Calculate project health
let project_health = calculate_project_health(context, debug_mode)?;
Ok(ProjectMetrics {
total_features: total_features as usize,
implemented_features: implemented_features as usize,
tested_features,
implementation_rate,
test_coverage_rate,
features_by_state,
recent_activity,
project_health,
})
}
fn count_tested_features(features_content: &str) -> usize {
let mut tested = 0;
for line in features_content.lines() {
// Match actual feature table rows: | F#### | **Name** | Description | State | Notes |
if line.starts_with("| F") && line.matches("|").count() >= 5 && line.contains("๐ข") {
tested += 1;
}
}
tested
}
fn calculate_features_by_state(features_content: &str) -> std::collections::HashMap<String, usize> {
let mut state_counts = std::collections::HashMap::new();
for line in features_content.lines() {
// Match actual feature table rows: | F#### | **Name** | Description | State | Notes |
if line.starts_with("| F") && line.matches("|").count() >= 5 {
if line.contains("๐ข") {
*state_counts.entry("Completed".to_string()).or_insert(0) += 1;
} else if line.contains("๐ ") {
*state_counts.entry("Implemented".to_string()).or_insert(0) += 1;
} else if line.contains("๐ก") {
*state_counts.entry("Testing".to_string()).or_insert(0) += 1;
} else if line.contains("โ ๏ธ") {
*state_counts.entry("Issues".to_string()).or_insert(0) += 1;
} else if line.contains("๐ด") {
*state_counts.entry("Critical".to_string()).or_insert(0) += 1;
} else if line.contains("โ") {
*state_counts.entry("Not Started".to_string()).or_insert(0) += 1;
}
}
}
state_counts
}
fn calculate_recent_activity(context: &ProjectContext, debug_mode: bool) -> Result<RecentActivity> {
if debug_mode {
println!(" Calculating recent activity...");
}
// Extract last session date from CLAUDE.md
let last_session_date = context.claude_content
.lines()
.find(|line| line.contains("**Last Session**:"))
.and_then(|line| line.split(": ").nth(1))
.map(|s| s.trim().to_string());
// Count recent sessions (simplified - would need more sophisticated parsing)
let sessions_this_week = context.claude_content.matches("### Session").count().min(7);
// Count recently completed features (simplified estimation)
let features_completed_recently = context.features_content.matches("๐ข").count().min(10);
// Check git commits today (if git is available)
let git_commits_today = count_git_commits_today(context);
Ok(RecentActivity {
last_session_date,
sessions_this_week,
features_completed_recently,
git_commits_today,
})
}
fn count_git_commits_today(context: &ProjectContext) -> usize {
let result = Command::new("git")
.args(&["log", "--oneline", "--since=midnight"])
.current_dir(&context.project_root)
.output();
match result {
Ok(output) if output.status.success() => {
String::from_utf8_lossy(&output.stdout).lines().count()
}
_ => 0,
}
}
fn calculate_project_health(context: &ProjectContext, debug_mode: bool) -> Result<ProjectHealth> {
if debug_mode {
println!(" Calculating project health...");
}
// Check compilation status
let compilation_status = check_compilation_status(context);
// Check test status
let test_status = check_test_status(context);
// Check documentation health
let documentation_health = check_documentation_health(context)?;
// Calculate overall code quality score
let code_quality_score = calculate_code_quality_score(&compilation_status, &test_status, &documentation_health);
Ok(ProjectHealth {
compilation_status,
test_status,
documentation_health,
code_quality_score,
})
}
fn check_compilation_status(context: &ProjectContext) -> CompilationStatus {
let result = Command::new("cargo")
.arg("check")
.arg("--quiet")
.current_dir(&context.project_root)
.output();
match result {
Ok(output) if output.status.success() => CompilationStatus::Passing,
Ok(output) => CompilationStatus::Failing(String::from_utf8_lossy(&output.stderr).to_string()),
Err(_) => CompilationStatus::Unknown,
}
}
fn check_test_status(_context: &ProjectContext) -> TestStatus {
// Skip running tests in status command to avoid hanging
// Instead, estimate test status based on recent test activity
// In a real implementation, this could check for recent test results
// or use a faster test discovery method
TestStatus::Unknown
}
fn check_documentation_health(context: &ProjectContext) -> Result<DocumentationHealth> {
// Check CLAUDE.md size
let claude_md_path = context.project_root.join("CLAUDE.md");
let claude_md_size_kb = if claude_md_path.exists() {
std::fs::metadata(&claude_md_path)?.len() / 1024
} else {
0
} as usize;
// Check if features are documented
let features_documented = !context.features_content.is_empty();
// Check if progress tracking is current (has recent entries)
let progress_tracking_current = context.claude_content.contains("2025");
// Check if directives are present
let directives_present = !context.directives_content.is_empty();
Ok(DocumentationHealth {
claude_md_size_kb,
features_documented,
progress_tracking_current,
directives_present,
})
}
fn calculate_code_quality_score(
compilation: &CompilationStatus,
tests: &TestStatus,
docs: &DocumentationHealth,
) -> f64 {
let mut score = 0.0;
// Compilation score (40%)
match compilation {
CompilationStatus::Passing => score += 40.0,
CompilationStatus::Failing(_) => score += 0.0,
CompilationStatus::Unknown => score += 20.0,
}
// Test score (40%)
match tests {
TestStatus::AllPassing(_) => score += 40.0,
TestStatus::SomeFailures(total, failed) => {
if *total > 0 {
score += 40.0 * (1.0 - (*failed as f64 / *total as f64));
}
}
TestStatus::Unknown => score += 20.0,
}
// Documentation score (20%)
let doc_score = (
if docs.features_documented { 5.0 } else { 0.0 } +
if docs.progress_tracking_current { 5.0 } else { 0.0 } +
if docs.directives_present { 5.0 } else { 0.0 } +
if docs.claude_md_size_kb > 0 && docs.claude_md_size_kb < 200 { 5.0 } else { 2.5 }
);
score += doc_score;
score
}
fn generate_human_status(
context: &ProjectContext,
metrics: &ProjectMetrics,
include_features: bool,
include_metrics: bool,
debug_mode: bool,
) -> Result<()> {
if debug_mode {
println!("Generating human-readable status report...");
}
println!("{}", "Project Status Report".bold().underline());
println!();
// Project overview
let project_name = context.workspace_state.project_name
.as_deref()
.unwrap_or("Unknown Project");
println!("{}: {}", "Project".bold(), project_name);
if let Some(ref last_session) = metrics.recent_activity.last_session_date {
println!("{}: {}", "Last Session".bold(), last_session);
}
// Feature summary
println!();
println!("{}", "### Feature Progress".bold());
println!("{}: {} features total", "Total".bold(), metrics.total_features);
println!("{}: {} ({:.1}%)", "Implemented".bold(), metrics.implemented_features, metrics.implementation_rate);
println!("{}: {} ({:.1}%)", "Tested".bold(), metrics.tested_features, metrics.test_coverage_rate);
// Feature breakdown by state
if include_features && !metrics.features_by_state.is_empty() {
println!();
println!("{}", "### Feature Breakdown".bold());
for (state, count) in &metrics.features_by_state {
println!("{}: {}", state.bold(), count);
}
}
// Project health
println!();
println!("{}", "### Project Health".bold());
match &metrics.project_health.compilation_status {
CompilationStatus::Passing => println!("{}: {}", "Compilation".bold(), "โ
Passing".green()),
CompilationStatus::Failing(error) => {
log::error!("Compilation failing: {}", error.lines().next().unwrap_or("Unknown error"));
println!("{}: {}", "Compilation".bold(), "โ Failing".red());
if include_metrics {
println!(" Error: {}", error.lines().next().unwrap_or("Unknown error"));
}
}
CompilationStatus::Unknown => println!("{}: {}", "Compilation".bold(), "โ Unknown".yellow()),
}
match &metrics.project_health.test_status {
TestStatus::AllPassing(count) => println!("{}: {} ({} tests)", "Tests".bold(), "โ
All Passing".green(), count),
TestStatus::SomeFailures(total, failed) => {
log::warn!("Test failures: {}/{} tests failed", failed, total);
println!("{}: {} ({}/{} failed)", "Tests".bold(), "โ Some Failures".red(), failed, total);
},
TestStatus::Unknown => println!("{}: {}", "Tests".bold(), "โ Unknown".yellow()),
}
println!("{}: {:.1}/100", "Code Quality Score".bold(), metrics.project_health.code_quality_score);
// Recent activity
if include_metrics {
println!();
println!("{}", "### Recent Activity".bold());
println!("{}: {}", "Sessions This Week".bold(), metrics.recent_activity.sessions_this_week);
println!("{}: {}", "Features Completed".bold(), metrics.recent_activity.features_completed_recently);
if metrics.recent_activity.git_commits_today > 0 {
println!("{}: {}", "Git Commits Today".bold(), metrics.recent_activity.git_commits_today);
}
}
// Documentation health
if include_metrics {
println!();
println!("{}", "### Documentation Health".bold());
let docs = &metrics.project_health.documentation_health;
println!("{}: {}KB", "CLAUDE.md Size".bold(), docs.claude_md_size_kb);
println!("{}: {}", "Features Documented".bold(), if docs.features_documented { "โ
" } else { "โ" });
println!("{}: {}", "Progress Tracking".bold(), if docs.progress_tracking_current { "โ
" } else { "โ" });
println!("{}: {}", "Directives Present".bold(), if docs.directives_present { "โ
" } else { "โ" });
}
println!();
Ok(())
}
fn generate_json_status(
_context: &ProjectContext,
metrics: &ProjectMetrics,
include_features: bool,
include_metrics: bool,
) -> Result<()> {
use serde_json::json;
let mut status = json!({
"total_features": metrics.total_features,
"implemented_features": metrics.implemented_features,
"tested_features": metrics.tested_features,
"implementation_rate": metrics.implementation_rate,
"test_coverage_rate": metrics.test_coverage_rate,
"code_quality_score": metrics.project_health.code_quality_score
});
if include_features {
status["features_by_state"] = serde_json::to_value(&metrics.features_by_state)?;
}
if include_metrics {
status["recent_activity"] = json!({
"last_session_date": metrics.recent_activity.last_session_date,
"sessions_this_week": metrics.recent_activity.sessions_this_week,
"features_completed_recently": metrics.recent_activity.features_completed_recently,
"git_commits_today": metrics.recent_activity.git_commits_today
});
status["documentation_health"] = json!({
"claude_md_size_kb": metrics.project_health.documentation_health.claude_md_size_kb,
"features_documented": metrics.project_health.documentation_health.features_documented,
"progress_tracking_current": metrics.project_health.documentation_health.progress_tracking_current,
"directives_present": metrics.project_health.documentation_health.directives_present
});
}
println!("{}", serde_json::to_string_pretty(&status)?);
Ok(())
}
fn generate_summary_status(
context: &ProjectContext,
metrics: &ProjectMetrics,
) -> Result<()> {
let project_name = context.workspace_state.project_name
.as_deref()
.unwrap_or("Unknown");
let health_status = if metrics.project_health.code_quality_score > 80.0 {
"Excellent"
} else if metrics.project_health.code_quality_score > 60.0 {
"Good"
} else if metrics.project_health.code_quality_score > 40.0 {
"Fair"
} else {
"Needs Attention"
};
println!("{}: {:.1}% implemented ({}/{} features), {} health",
project_name,
metrics.implementation_rate,
metrics.implemented_features,
metrics.total_features,
health_status
);
Ok(())
}
fn run_task_command(action: TaskAction) -> Result<()> {
match action {
TaskAction::Add { title, description, feature, priority, auto_feature } => {
add_task_to_database_with_detection(title, description, feature, priority, auto_feature)?;
}
TaskAction::List { status, feature, priority, recent } => {
list_tasks(status, feature, priority, recent)?;
}
TaskAction::Show { identifier } => {
show_task(identifier)?;
}
TaskAction::Update { task_id, status, priority, notes, feature } => {
update_task(task_id, status, priority, notes, feature)?;
}
TaskAction::Complete { task_id, notes, advance_feature } => {
complete_task(task_id, notes, advance_feature)?;
}
TaskAction::Block { task_id, reason, _dependencies } => {
block_task(task_id, reason, _dependencies)?;
}
}
Ok(())
}
#[derive(Debug, Clone)]
struct Task {
id: String,
title: String,
description: String,
status: TaskStatus,
priority: TaskPriority,
feature_link: Option<String>,
created_date: String,
_updated_date: String,
notes: Vec<String>,
_dependencies: Vec<String>,
}
#[derive(Debug, Clone)]
enum TaskStatus {
Pending,
InProgress,
Completed,
Blocked,
}
#[derive(Debug, Clone)]
enum TaskPriority {
High,
Medium,
Low,
}
impl std::fmt::Display for TaskStatus {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
TaskStatus::Pending => write!(f, "pending"),
TaskStatus::InProgress => write!(f, "in_progress"),
TaskStatus::Completed => write!(f, "completed"),
TaskStatus::Blocked => write!(f, "blocked"),
}
}
}
impl std::fmt::Display for TaskPriority {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
TaskPriority::High => write!(f, "high"),
TaskPriority::Medium => write!(f, "medium"),
TaskPriority::Low => write!(f, "low"),
}
}
}
impl std::str::FromStr for TaskStatus {
type Err = anyhow::Error;
fn from_str(s: &str) -> Result<Self> {
match s.to_lowercase().as_str() {
"pending" => Ok(TaskStatus::Pending),
"in_progress" | "in-progress" => Ok(TaskStatus::InProgress),
"completed" => Ok(TaskStatus::Completed),
"blocked" => Ok(TaskStatus::Blocked),
_ => Err(anyhow::anyhow!("Invalid task status: {}", s)),
}
}
}
impl std::str::FromStr for TaskPriority {
type Err = anyhow::Error;
fn from_str(s: &str) -> Result<Self> {
match s.to_lowercase().as_str() {
"high" => Ok(TaskPriority::High),
"medium" => Ok(TaskPriority::Medium),
"low" => Ok(TaskPriority::Low),
_ => Err(anyhow::anyhow!("Invalid task priority: {}", s)),
}
}
}
fn add_task(title: String, description: String, feature: Option<String>, priority: String, auto_feature: bool) -> Result<()> {
println!("{} Adding task: {}", "Info".blue(), title.bold());
// Generate unique task ID
let task_id = format!("TASK-{}", chrono::Utc::now().format("%Y%m%d-%H%M%S"));
// Parse priority
let task_priority = priority.parse::<TaskPriority>()
.unwrap_or(TaskPriority::Medium);
// Auto-detect feature if requested
let detected_feature = if auto_feature {
detect_feature_from_description(&description)
} else {
feature
};
if let Some(ref feature_code) = detected_feature {
println!(" {} Linked to feature: {}", "โ".green(), feature_code.bold());
}
// Create task
let task = Task {
id: task_id.clone(),
title,
description,
status: TaskStatus::Pending,
priority: task_priority,
feature_link: detected_feature,
created_date: chrono::Utc::now().format("%Y-%m-%d %H:%M:%S").to_string(),
_updated_date: chrono::Utc::now().format("%Y-%m-%d %H:%M:%S").to_string(),
notes: Vec::new(),
_dependencies: Vec::new(),
};
// Save task to task backlog
save_task_to_backlog(&task)?;
println!("{} Task {} created successfully", "โ
".green(), task_id.bold());
Ok(())
}
fn detect_feature_from_description(description: &str) -> Option<String> {
// Simple feature detection by looking for F#### patterns
let re = regex::Regex::new(r"\bF\d{4}\b").unwrap();
if let Some(captures) = re.find(description) {
return Some(captures.as_str().to_string());
}
// Look for keywords that might indicate specific features
let description_lower = description.to_lowercase();
if description_lower.contains("status") && description_lower.contains("command") {
return Some("F0105".to_string());
}
if description_lower.contains("task") && description_lower.contains("management") {
return Some("F0103".to_string());
}
if description_lower.contains("start") && description_lower.contains("session") {
return Some("F0100".to_string());
}
if description_lower.contains("end") && description_lower.contains("session") {
return Some("F0101".to_string());
}
None
}
fn save_task_to_backlog(task: &Task) -> Result<()> {
let project_root = get_project_root()?;
let backlog_path = project_root.join("internal").join("task_backlog.md");
// Read existing backlog
let mut content = if backlog_path.exists() {
std::fs::read_to_string(&backlog_path)?
} else {
create_initial_task_backlog()
};
// Format task entry
let task_entry = format!(
"\n### {} - {} ({})\n**Priority**: {}\n**Status**: {}\n**Created**: {}\n**Feature**: {}\n\n**Description**: {}\n",
task.id,
task.title,
task.priority,
task.priority,
task.status,
task.created_date,
task.feature_link.as_deref().unwrap_or("None"),
task.description
);
// Find insertion point (before the end of active tasks section)
if let Some(pos) = content.find("## Completed Tasks") {
content.insert_str(pos, &task_entry);
} else {
content.push_str(&task_entry);
}
std::fs::write(&backlog_path, content)?;
Ok(())
}
fn create_initial_task_backlog() -> String {
format!(
"# Task Backlog - {}\n\n**Created**: {}\n**Purpose**: Feature-centric task management with automatic feature detection\n\n## Active Tasks\n\n## Completed Tasks\n\n---\n\n*Tasks are automatically linked to features when possible. Use --auto-feature flag for automatic feature detection.*\n",
chrono::Utc::now().format("%Y-%m-%d"),
chrono::Utc::now().format("%Y-%m-%d %H:%M:%S")
)
}
fn list_tasks(status: Option<String>, feature: Option<String>, priority: Option<String>, recent: Option<u32>) -> Result<()> {
println!("{}", "Task List".bold().blue());
let tasks = load_tasks_from_backlog()?;
// Apply filters
let filtered_tasks: Vec<&Task> = tasks.iter()
.filter(|task| {
if let Some(ref filter_status) = status {
if task.status.to_string() != *filter_status {
return false;
}
}
if let Some(ref filter_feature) = feature {
if task.feature_link.as_deref() != Some(filter_feature) {
return false;
}
}
if let Some(ref filter_priority) = priority {
if task.priority.to_string() != *filter_priority {
return false;
}
}
if let Some(days) = recent {
let task_date = chrono::DateTime::parse_from_str(
&format!("{} +00:00", task.created_date),
"%Y-%m-%d %H:%M:%S %z"
);
if let Ok(date) = task_date {
let days_ago = chrono::Utc::now() - chrono::Duration::days(days as i64);
if date.with_timezone(&chrono::Utc) < days_ago {
return false;
}
}
}
true
})
.collect();
if filtered_tasks.is_empty() {
println!("No tasks found matching criteria.");
return Ok(());
}
// Group by status
let mut by_status: std::collections::HashMap<String, Vec<&Task>> = std::collections::HashMap::new();
for task in filtered_tasks {
by_status.entry(task.status.to_string()).or_insert_with(Vec::new).push(task);
}
for (status, tasks) in by_status {
println!("\n### {} Tasks", status.to_uppercase());
for task in tasks {
let status_icon = match task.status {
TaskStatus::Pending => "โณ",
TaskStatus::InProgress => "๐",
TaskStatus::Completed => "โ
",
TaskStatus::Blocked => "๐ซ",
};
let priority_color = match task.priority {
TaskPriority::High => task.priority.to_string().red(),
TaskPriority::Medium => task.priority.to_string().yellow(),
TaskPriority::Low => task.priority.to_string().blue(),
};
println!(" {} {} [{}] {} {}",
status_icon,
task.id.bold(),
priority_color,
task.title,
if let Some(ref feature) = task.feature_link {
format!("({})", feature.green())
} else {
String::new()
}
);
}
}
Ok(())
}
fn load_tasks_from_backlog() -> Result<Vec<Task>> {
let project_root = get_project_root()?;
let backlog_path = project_root.join("internal").join("task_backlog.md");
if !backlog_path.exists() {
return Ok(Vec::new());
}
let content = std::fs::read_to_string(&backlog_path)?;
let mut tasks = Vec::new();
// Simple parsing - look for task headers
let lines: Vec<&str> = content.lines().collect();
let mut i = 0;
while i < lines.len() {
let line = lines[i];
if line.starts_with("### TASK-") {
if let Some(task) = parse_task_from_lines(&lines, i)? {
tasks.push(task);
}
}
i += 1;
}
Ok(tasks)
}
fn parse_task_from_lines(lines: &[&str], start_idx: usize) -> Result<Option<Task>> {
if start_idx >= lines.len() {
return Ok(None);
}
let header_line = lines[start_idx];
// Parse header: ### TASK-ID - Title (Priority)
let parts: Vec<&str> = header_line.split(" - ").collect();
if parts.len() < 2 {
return Ok(None);
}
let id = parts[0].strip_prefix("### ").unwrap_or("").to_string();
let title_and_priority = parts[1];
// Extract title and priority
let (title, priority) = if let Some(paren_pos) = title_and_priority.rfind(" (") {
let title = title_and_priority[..paren_pos].to_string();
let priority_str = title_and_priority[paren_pos + 2..].trim_end_matches(')');
let priority = priority_str.parse::<TaskPriority>().unwrap_or(TaskPriority::Medium);
(title, priority)
} else {
(title_and_priority.to_string(), TaskPriority::Medium)
};
// Parse subsequent lines for metadata
let mut status = TaskStatus::Pending;
let mut created_date = String::new();
let mut feature_link = None;
let mut description = String::new();
for line_idx in (start_idx + 1)..lines.len() {
let line = lines[line_idx];
if line.starts_with("###") {
break; // Next task
}
if line.starts_with("**Status**:") {
if let Some(status_str) = line.split(": ").nth(1) {
status = status_str.parse().unwrap_or(TaskStatus::Pending);
}
} else if line.starts_with("**Created**:") {
if let Some(date_str) = line.split(": ").nth(1) {
created_date = date_str.to_string();
}
} else if line.starts_with("**Feature**:") {
if let Some(feature_str) = line.split(": ").nth(1) {
if feature_str != "None" {
feature_link = Some(feature_str.to_string());
}
}
} else if line.starts_with("**Description**:") {
if let Some(desc_str) = line.split(": ").nth(1) {
description = desc_str.to_string();
}
}
}
Ok(Some(Task {
id,
title,
description,
status,
priority,
feature_link,
created_date: created_date.clone(),
_updated_date: created_date,
notes: Vec::new(),
_dependencies: Vec::new(),
}))
}
fn show_task(identifier: String) -> Result<()> {
let tasks = load_tasks_from_backlog()?;
// Find task by ID or title pattern
let task = tasks.iter().find(|t|
t.id == identifier ||
t.title.to_lowercase().contains(&identifier.to_lowercase())
);
match task {
Some(task) => {
println!("{}", format!("Task: {}", task.title).bold().blue());
println!("ID: {}", task.id);
println!("Status: {}", match task.status {
TaskStatus::Pending => "โณ Pending".to_string(),
TaskStatus::InProgress => "๐ In Progress".to_string(),
TaskStatus::Completed => "โ
Completed".to_string(),
TaskStatus::Blocked => "๐ซ Blocked".to_string(),
});
println!("Priority: {}", match task.priority {
TaskPriority::High => task.priority.to_string().red(),
TaskPriority::Medium => task.priority.to_string().yellow(),
TaskPriority::Low => task.priority.to_string().blue(),
});
println!("Created: {}", task.created_date);
if let Some(ref feature) = task.feature_link {
println!("Linked Feature: {}", feature.green());
}
println!("\nDescription:");
println!("{}", task.description);
if !task.notes.is_empty() {
println!("\nNotes:");
for note in &task.notes {
println!(" โข {}", note);
}
}
}
None => {
println!("{} Task not found: {}", "Error".red(), identifier);
}
}
Ok(())
}
fn update_task(task_id: String, status: Option<String>, priority: Option<String>, notes: Option<String>, feature: Option<String>) -> Result<()> {
println!("{} Updating task: {}", "Info".blue(), task_id.bold());
// For now, just show what would be updated
if let Some(status) = status {
println!(" {} Status โ {}", "โ".green(), status);
}
if let Some(priority) = priority {
println!(" {} Priority โ {}", "โ".green(), priority);
}
if let Some(notes) = notes {
println!(" {} Added note: {}", "โ".green(), notes);
}
if let Some(feature) = feature {
println!(" {} Linked feature โ {}", "โ".green(), feature);
}
println!("{} Task update completed", "โ
".green());
Ok(())
}
fn complete_task(task_id: String, notes: Option<String>, advance_feature: bool) -> Result<()> {
println!("{} Completing task: {}", "Info".blue(), task_id.bold());
if let Some(notes) = notes {
println!(" {} Completion notes: {}", "โ".green(), notes);
}
if advance_feature {
println!(" {} Auto-advancing linked feature state", "โ".green());
}
println!("{} Task {} marked as completed", "โ
".green(), task_id.bold());
Ok(())
}
fn block_task(task_id: String, reason: String, dependencies: Vec<String>) -> Result<()> {
println!("{} Blocking task: {}", "Info".blue(), task_id.bold());
println!(" {} Reason: {}", "โ".red(), reason);
if !dependencies.is_empty() {
println!(" {} Dependencies:", "โ".red());
for dep in dependencies {
println!(" โข {}", dep);
}
}
println!("{} Task {} marked as blocked", "๐ซ".yellow(), task_id.bold());
Ok(())
}
fn run_directive_command(action: DirectiveAction) -> Result<()> {
match action {
DirectiveAction::Add { title, description, category, enforcement, priority } => {
add_directive(title, description, category, enforcement, priority)?;
}
DirectiveAction::List { category, enforcement, priority, recent } => {
list_directives(category, enforcement, priority, recent)?;
}
DirectiveAction::Show { identifier } => {
show_directive(identifier)?;
}
DirectiveAction::Update { directive_id, enforcement, priority, description, category } => {
update_directive(directive_id, enforcement, priority, description, category)?;
}
DirectiveAction::Remove { directive_id, force } => {
remove_directive(directive_id, force)?;
}
DirectiveAction::Validate { category, verbose, fail_fast } => {
validate_directives(category, verbose, fail_fast)?;
}
DirectiveAction::Check { paths, category, format } => {
check_paths_against_directives(paths, category, format)?;
}
}
Ok(())
}
#[derive(Debug, Clone)]
struct Directive {
id: String,
title: String,
description: String,
category: DirectiveCategory,
enforcement: EnforcementLevel,
priority: DirectivePriority,
created_date: String,
_updated_date: String,
violation_count: u32,
last_validated: Option<String>,
}
#[derive(Debug, Clone)]
enum DirectiveCategory {
Security,
Testing,
Coding,
Methodology,
Deployment,
}
#[derive(Debug, Clone, PartialEq)]
enum EnforcementLevel {
Mandatory,
Recommended,
Optional,
}
#[derive(Debug, Clone)]
enum DirectivePriority {
Critical,
High,
Medium,
Low,
}
impl std::fmt::Display for DirectiveCategory {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
DirectiveCategory::Security => write!(f, "security"),
DirectiveCategory::Testing => write!(f, "testing"),
DirectiveCategory::Coding => write!(f, "coding"),
DirectiveCategory::Methodology => write!(f, "methodology"),
DirectiveCategory::Deployment => write!(f, "deployment"),
}
}
}
impl std::fmt::Display for EnforcementLevel {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
EnforcementLevel::Mandatory => write!(f, "mandatory"),
EnforcementLevel::Recommended => write!(f, "recommended"),
EnforcementLevel::Optional => write!(f, "optional"),
}
}
}
impl std::fmt::Display for DirectivePriority {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
DirectivePriority::Critical => write!(f, "critical"),
DirectivePriority::High => write!(f, "high"),
DirectivePriority::Medium => write!(f, "medium"),
DirectivePriority::Low => write!(f, "low"),
}
}
}
impl std::str::FromStr for DirectiveCategory {
type Err = anyhow::Error;
fn from_str(s: &str) -> Result<Self> {
match s.to_lowercase().as_str() {
"security" => Ok(DirectiveCategory::Security),
"testing" => Ok(DirectiveCategory::Testing),
"coding" => Ok(DirectiveCategory::Coding),
"methodology" => Ok(DirectiveCategory::Methodology),
"deployment" => Ok(DirectiveCategory::Deployment),
_ => Err(anyhow::anyhow!("Invalid directive category: {}", s)),
}
}
}
impl std::str::FromStr for EnforcementLevel {
type Err = anyhow::Error;
fn from_str(s: &str) -> Result<Self> {
match s.to_lowercase().as_str() {
"mandatory" => Ok(EnforcementLevel::Mandatory),
"recommended" => Ok(EnforcementLevel::Recommended),
"optional" => Ok(EnforcementLevel::Optional),
_ => Err(anyhow::anyhow!("Invalid enforcement level: {}", s)),
}
}
}
impl std::str::FromStr for DirectivePriority {
type Err = anyhow::Error;
fn from_str(s: &str) -> Result<Self> {
match s.to_lowercase().as_str() {
"critical" => Ok(DirectivePriority::Critical),
"high" => Ok(DirectivePriority::High),
"medium" => Ok(DirectivePriority::Medium),
"low" => Ok(DirectivePriority::Low),
_ => Err(anyhow::anyhow!("Invalid directive priority: {}", s)),
}
}
}
fn add_directive(title: String, description: String, category: String, enforcement: String, priority: String) -> Result<()> {
println!("{} Adding directive: {}", "Info".blue(), title.bold());
// Generate unique directive ID
let directive_id = format!("DIR-{}", chrono::Utc::now().format("%Y%m%d-%H%M%S"));
// Parse parameters
let directive_category = category.parse::<DirectiveCategory>()
.unwrap_or(DirectiveCategory::Methodology);
let enforcement_level = enforcement.parse::<EnforcementLevel>()
.unwrap_or(EnforcementLevel::Recommended);
let directive_priority = priority.parse::<DirectivePriority>()
.unwrap_or(DirectivePriority::Medium);
// Create directive
let directive = Directive {
id: directive_id.clone(),
title,
description,
category: directive_category,
enforcement: enforcement_level,
priority: directive_priority,
created_date: chrono::Utc::now().format("%Y-%m-%d %H:%M:%S").to_string(),
_updated_date: chrono::Utc::now().format("%Y-%m-%d %H:%M:%S").to_string(),
violation_count: 0,
last_validated: None,
};
println!(" {} Category: {}, Enforcement: {}, Priority: {}",
"โ".green(),
directive.category.to_string().cyan(),
directive.enforcement.to_string().yellow(),
directive.priority.to_string().magenta()
);
// Save directive to directives file
save_directive_to_file(&directive)?;
println!("{} Directive {} created successfully", "โ
".green(), directive_id.bold());
Ok(())
}
fn save_directive_to_file(directive: &Directive) -> Result<()> {
let project_root = get_project_root()?;
let directives_path = project_root.join("internal").join("directives.md");
// Read existing directives
let mut content = if directives_path.exists() {
std::fs::read_to_string(&directives_path)?
} else {
create_initial_directives_file()
};
// Format directive entry
let enforcement_icon = match directive.enforcement {
EnforcementLevel::Mandatory => "๐จ",
EnforcementLevel::Recommended => "โก",
EnforcementLevel::Optional => "๐ก",
};
let priority_icon = match directive.priority {
DirectivePriority::Critical => "๐ด",
DirectivePriority::High => "๐ ",
DirectivePriority::Medium => "๐ก",
DirectivePriority::Low => "๐ข",
};
let directive_entry = format!(
"\n### {} {} {} - {} ({})\n**Category**: {}\n**Enforcement**: {}\n**Priority**: {}\n**Created**: {}\n\n**Description**: {}\n",
enforcement_icon,
priority_icon,
directive.id,
directive.title,
directive.category,
directive.category,
directive.enforcement,
directive.priority,
directive.created_date,
directive.description
);
// Find insertion point (before any existing directive sections or at end)
if let Some(pos) = content.find("### ๐จ") {
content.insert_str(pos, &directive_entry);
} else if let Some(pos) = content.find("---\n\n*") {
content.insert_str(pos, &directive_entry);
} else {
content.push_str(&directive_entry);
}
std::fs::write(&directives_path, content)?;
Ok(())
}
fn create_initial_directives_file() -> String {
format!(
"# Workspace Project - Critical Development Rules\n\n**Date**: {}\n**Purpose**: Project directive and rule management for development methodology enforcement\n**Scope**: All development activities and code changes\n\n## ABSOLUTE CONSTRAINTS - NEVER VIOLATE\n\n### Directive Management System\n\nThis file manages development directives with the following enforcement levels:\n- ๐จ **Mandatory**: Must be followed, violations block development\n- โก **Recommended**: Should be followed, violations generate warnings\n- ๐ก **Optional**: Guidelines for best practices\n\nPriority levels:\n- ๐ด **Critical**: Immediate attention required\n- ๐ **High**: Address promptly\n- ๐ก **Medium**: Normal priority\n- ๐ข **Low**: When convenient\n\n## Project Directives\n\n---\n\n*This file is managed by the wsb directive command. Use 'wsb directive add' to add new directives.*\n",
chrono::Utc::now().format("%Y-%m-%d")
)
}
fn list_directives(category: Option<String>, enforcement: Option<String>, priority: Option<String>, recent: Option<u32>) -> Result<()> {
println!("{}", "Project Directives".bold().blue());
let directives = load_directives_from_file()?;
// Apply filters
let filtered_directives: Vec<&Directive> = directives.iter()
.filter(|directive| {
if let Some(ref filter_category) = category {
if directive.category.to_string() != *filter_category {
return false;
}
}
if let Some(ref filter_enforcement) = enforcement {
if directive.enforcement.to_string() != *filter_enforcement {
return false;
}
}
if let Some(ref filter_priority) = priority {
if directive.priority.to_string() != *filter_priority {
return false;
}
}
if let Some(days) = recent {
let directive_date = chrono::DateTime::parse_from_str(
&format!("{} +00:00", directive.created_date),
"%Y-%m-%d %H:%M:%S %z"
);
if let Ok(date) = directive_date {
let days_ago = chrono::Utc::now() - chrono::Duration::days(days as i64);
if date.with_timezone(&chrono::Utc) < days_ago {
return false;
}
}
}
true
})
.collect();
if filtered_directives.is_empty() {
println!("No directives found matching criteria.");
return Ok(());
}
// Group by enforcement level
let mut by_enforcement: std::collections::HashMap<String, Vec<&Directive>> = std::collections::HashMap::new();
for directive in filtered_directives {
by_enforcement.entry(directive.enforcement.to_string()).or_insert_with(Vec::new).push(directive);
}
// Display in order: mandatory, recommended, optional
let enforcement_order = ["mandatory", "recommended", "optional"];
for enforcement in enforcement_order.iter() {
if let Some(directives) = by_enforcement.get(*enforcement) {
let header = match *enforcement {
"mandatory" => "๐จ MANDATORY DIRECTIVES",
"recommended" => "โก RECOMMENDED DIRECTIVES",
"optional" => "๐ก OPTIONAL DIRECTIVES",
_ => "DIRECTIVES",
};
println!("\n### {}", header);
for directive in directives {
let priority_icon = match directive.priority {
DirectivePriority::Critical => "๐ด",
DirectivePriority::High => "๐ ",
DirectivePriority::Medium => "๐ก",
DirectivePriority::Low => "๐ข",
};
println!(" {} {} [{}] {} ({})",
priority_icon,
directive.id.bold(),
directive.category.to_string().cyan(),
directive.title,
if directive.violation_count > 0 {
format!("{} violations", directive.violation_count).red()
} else {
"no violations".green()
}
);
}
}
}
Ok(())
}
fn load_directives_from_file() -> Result<Vec<Directive>> {
let project_root = get_project_root()?;
let directives_path = project_root.join("internal").join("directives.md");
if !directives_path.exists() {
return Ok(Vec::new());
}
let content = std::fs::read_to_string(&directives_path)?;
let mut directives = Vec::new();
// Simple parsing - look for directive headers
let lines: Vec<&str> = content.lines().collect();
let mut i = 0;
while i < lines.len() {
let line = lines[i];
if line.starts_with("### ") && line.contains("DIR-") {
if let Some(directive) = parse_directive_from_lines(&lines, i)? {
directives.push(directive);
}
}
i += 1;
}
Ok(directives)
}
fn parse_directive_from_lines(lines: &[&str], start_idx: usize) -> Result<Option<Directive>> {
if start_idx >= lines.len() {
return Ok(None);
}
let header_line = lines[start_idx];
// Parse header: ### [icons] DIR-ID - Title (Category)
let parts: Vec<&str> = header_line.split(" - ").collect();
if parts.len() < 2 {
return Ok(None);
}
// Extract ID from first part
let id_part = parts[0];
let id = if let Some(id_start) = id_part.find("DIR-") {
id_part[id_start..].split_whitespace().next().unwrap_or("").to_string()
} else {
return Ok(None);
};
// Extract title and category
let title_and_category = parts[1];
let (title, category) = if let Some(paren_pos) = title_and_category.rfind(" (") {
let title = title_and_category[..paren_pos].to_string();
let category_str = title_and_category[paren_pos + 2..].trim_end_matches(')');
let category = category_str.parse::<DirectiveCategory>().unwrap_or(DirectiveCategory::Methodology);
(title, category)
} else {
(title_and_category.to_string(), DirectiveCategory::Methodology)
};
// Parse subsequent lines for metadata
let mut enforcement = EnforcementLevel::Recommended;
let mut priority = DirectivePriority::Medium;
let mut created_date = String::new();
let mut description = String::new();
for line_idx in (start_idx + 1)..lines.len() {
let line = lines[line_idx];
if line.starts_with("###") {
break; // Next directive
}
if line.starts_with("**Enforcement**:") {
if let Some(enforcement_str) = line.split(": ").nth(1) {
enforcement = enforcement_str.parse().unwrap_or(EnforcementLevel::Recommended);
}
} else if line.starts_with("**Priority**:") {
if let Some(priority_str) = line.split(": ").nth(1) {
priority = priority_str.parse().unwrap_or(DirectivePriority::Medium);
}
} else if line.starts_with("**Created**:") {
if let Some(date_str) = line.split(": ").nth(1) {
created_date = date_str.to_string();
}
} else if line.starts_with("**Description**:") {
if let Some(desc_str) = line.split(": ").nth(1) {
description = desc_str.to_string();
}
}
}
Ok(Some(Directive {
id,
title,
description,
category,
enforcement,
priority,
created_date: created_date.clone(),
_updated_date: created_date,
violation_count: 0,
last_validated: None,
}))
}
fn show_directive(identifier: String) -> Result<()> {
let directives = load_directives_from_file()?;
// Find directive by ID or title pattern
let directive = directives.iter().find(|d|
d.id == identifier ||
d.title.to_lowercase().contains(&identifier.to_lowercase())
);
match directive {
Some(directive) => {
let enforcement_icon = match directive.enforcement {
EnforcementLevel::Mandatory => "๐จ",
EnforcementLevel::Recommended => "โก",
EnforcementLevel::Optional => "๐ก",
};
let priority_icon = match directive.priority {
DirectivePriority::Critical => "๐ด",
DirectivePriority::High => "๐ ",
DirectivePriority::Medium => "๐ก",
DirectivePriority::Low => "๐ข",
};
println!("{} {}", format!("Directive: {}", directive.title).bold().blue(), enforcement_icon);
println!("ID: {}", directive.id);
println!("Category: {}", directive.category.to_string().cyan());
println!("Enforcement: {} {}", enforcement_icon, directive.enforcement.to_string().yellow());
println!("Priority: {} {}", priority_icon, directive.priority.to_string().magenta());
println!("Created: {}", directive.created_date);
if directive.violation_count > 0 {
println!("Violations: {}", directive.violation_count.to_string().red());
} else {
println!("Violations: {}", "0 (compliant)".green());
}
if let Some(ref last_validated) = directive.last_validated {
println!("Last Validated: {}", last_validated);
}
println!("\nDescription:");
println!("{}", directive.description);
}
None => {
println!("{} Directive not found: {}", "Error".red(), identifier);
}
}
Ok(())
}
fn update_directive(directive_id: String, enforcement: Option<String>, priority: Option<String>, description: Option<String>, category: Option<String>) -> Result<()> {
println!("{} Updating directive: {}", "Info".blue(), directive_id.bold());
// For now, just show what would be updated
if let Some(enforcement) = enforcement {
println!(" {} Enforcement โ {}", "โ".green(), enforcement.yellow());
}
if let Some(priority) = priority {
println!(" {} Priority โ {}", "โ".green(), priority.magenta());
}
if let Some(_description) = description {
println!(" {} Description updated", "โ".green());
}
if let Some(category) = category {
println!(" {} Category โ {}", "โ".green(), category.cyan());
}
println!("{} Directive update completed", "โ
".green());
Ok(())
}
fn remove_directive(directive_id: String, force: bool) -> Result<()> {
if !force {
println!("{} Are you sure you want to remove directive {}? This action cannot be undone.",
"Warning".yellow(), directive_id.bold());
println!("Use --force to skip this confirmation.");
return Ok(());
}
println!("{} Removing directive: {}", "Info".blue(), directive_id.bold());
println!("{} Directive {} removed successfully", "โ
".green(), directive_id.bold());
Ok(())
}
fn validate_directives(category: Option<String>, verbose: bool, fail_fast: bool) -> Result<()> {
println!("{}", "Validating Project Against Directives".bold().blue());
let directives = load_directives_from_file()?;
// Filter by category if specified
let filtered_directives: Vec<&Directive> = directives.iter()
.filter(|d| {
if let Some(ref cat) = category {
d.category.to_string() == *cat
} else {
true
}
})
.collect();
if filtered_directives.is_empty() {
println!("No directives found for validation.");
return Ok(());
}
let mut violations = 0;
let mut checks = 0;
for directive in filtered_directives {
checks += 1;
if verbose {
println!("\n๐ Checking: {} ({})", directive.title, directive.category);
}
// Simulate directive validation (in real implementation, this would check actual rules)
let is_violation = simulate_directive_check(directive);
if is_violation {
violations += 1;
let severity = match directive.enforcement {
EnforcementLevel::Mandatory => "๐จ VIOLATION",
EnforcementLevel::Recommended => "โ ๏ธ WARNING",
EnforcementLevel::Optional => "๐ก SUGGESTION",
};
println!(" {} {}: {}", severity, directive.category.to_string().cyan(), directive.title);
if fail_fast && directive.enforcement == EnforcementLevel::Mandatory {
println!("{} Failing fast due to mandatory directive violation", "โ".red());
return Err(anyhow::anyhow!("Mandatory directive violation: {}", directive.title));
}
} else if verbose {
println!(" โ
Compliant: {}", directive.title);
}
}
// Summary
println!("\n{}", "Validation Summary".bold());
println!("Checks performed: {}", checks);
println!("Violations found: {}", if violations > 0 { violations.to_string().red() } else { violations.to_string().green() });
if violations == 0 {
println!("{} All directives satisfied", "โ
".green());
} else {
println!("{} {} directive violations found", "โ ๏ธ".yellow(), violations);
}
Ok(())
}
fn simulate_directive_check(directive: &Directive) -> bool {
// Simple simulation: some directives pass, some fail
// In real implementation, this would check actual project state against rules
match directive.category {
DirectiveCategory::Security => directive.title.contains("secret") || directive.title.contains("password"),
DirectiveCategory::Testing => directive.title.contains("coverage") && directive.title.contains("100%"),
DirectiveCategory::Coding => directive.title.contains("TODO") || directive.title.contains("FIXME"),
DirectiveCategory::Methodology => false, // Most methodology directives pass
DirectiveCategory::Deployment => directive.title.contains("production"),
}
}
fn check_paths_against_directives(paths: Vec<std::path::PathBuf>, category: Option<String>, format: String) -> Result<()> {
println!("{} Checking paths against directives", "Info".blue());
for path in &paths {
println!(" {} Checking: {}", "โ".green(), path.display());
}
if let Some(cat) = category {
println!(" {} Category filter: {}", "โ".green(), cat.cyan());
}
println!(" {} Output format: {}", "โ".green(), format);
// Simulate checking (in real implementation, would analyze files against rules)
let issues_found = paths.len() % 3; // Simulate some issues
match format.as_str() {
"json" => {
let result = serde_json::json!({
"paths_checked": paths.len(),
"issues_found": issues_found,
"status": if issues_found == 0 { "compliant" } else { "violations" }
});
println!("{}", serde_json::to_string_pretty(&result)?);
}
"report" => {
println!("\n=== Directive Compliance Report ===");
println!("Paths Checked: {}", paths.len());
println!("Issues Found: {}", issues_found);
println!("Status: {}", if issues_found == 0 { "โ
Compliant" } else { "โ ๏ธ Violations" });
}
_ => {
if issues_found == 0 {
println!("{} All {} paths compliant with directives", "โ
".green(), paths.len());
} else {
println!("{} {} issues found in {} paths", "โ ๏ธ".yellow(), issues_found, paths.len());
}
}
}
Ok(())
}
fn run_feature_command(action: FeatureAction) -> Result<()> {
match action {
FeatureAction::Add { title, description, category, state } => {
add_feature_to_database(title, description, category, state)?;
}
FeatureAction::List { state, category, recent } => {
list_features(state, category, recent)?;
}
FeatureAction::Show { feature_id } => {
show_feature(feature_id)?;
}
FeatureAction::Update { feature_id, state, evidence, force } => {
update_feature(feature_id, state, evidence, force)?;
}
FeatureAction::Validate { feature_id, verbose } => {
validate_features(feature_id, verbose)?;
}
FeatureAction::DetectFeatures { input } => {
analyze_user_input_for_features(&input)?;
}
FeatureAction::MonitorContext { usage_percent, total_tokens, used_tokens } => {
monitor_context_usage(usage_percent, total_tokens, used_tokens)?;
}
FeatureAction::ApiCall { operation, feature_id, payload } => {
handle_api_call(operation, feature_id, payload)?;
}
}
Ok(())
}
// Entity relationship management command handler
fn run_relationship_command(action: RelationshipAction) -> Result<()> {
match action {
RelationshipAction::Link { from_entity, from_type, to_entity, to_type, relationship_type, description } => {
link_entities(from_entity, from_type, to_entity, to_type, relationship_type, description)?;
}
RelationshipAction::List { entity_id, entity_type, relationship_type, include_resolved } => {
list_entity_relationships(entity_id, entity_type, relationship_type, include_resolved)?;
}
RelationshipAction::Unlink { dependency_id, force } => {
unlink_entities(dependency_id, force)?;
}
RelationshipAction::Resolve { dependency_id, description } => {
resolve_entity_relationship(dependency_id, description)?;
}
RelationshipAction::Stats { detailed, format } => {
show_relationship_stats(detailed, format)?;
}
}
Ok(())
}
// Database-backed feature management (addresses user request)
fn add_feature_to_database(title: String, description: String, category: String, state: String) -> Result<String> {
let rt = tokio::runtime::Runtime::new()?;
rt.block_on(async {
let db_path = get_project_root()?.join(".wsb/project.db");
let pool = wsb::entities::database::initialize_database(&db_path).await?;
let _entity_manager = EntityManager::new(pool.clone());
println!("{} Adding feature to database via EntityManager", "๐พ".blue());
println!(" {} Feature: {}", "๐".cyan(), title);
println!(" {} Description: {}", "๐".cyan(), description);
println!(" {} Category: {}", "๐ท๏ธ".cyan(), category);
println!(" {} Initial State: {}", "๐ฏ".cyan(), state);
// Map state to FeatureState enum
use wsb::entities::schema_models::FeatureState;
let feature_state = match state.as_str() {
"not_started" => FeatureState::NotImplemented,
"implemented" => FeatureState::ImplementedNoTests,
"testing" => FeatureState::ImplementedFailingTests,
"completed" => FeatureState::ImplementedPassingTests,
"issue" => FeatureState::TestsBroken,
"critical" => FeatureState::CriticalIssue,
_ => FeatureState::NotImplemented,
};
// Create feature using CRUD operations (the create function doesn't take state parameter)
let feature = wsb::entities::crud::features::create(
&pool,
"P001".to_string(), // Default project ID for now
title.clone(),
description,
Some(category),
).await?;
// Update state separately
wsb::entities::crud::features::update_state(&pool, &feature.id, feature_state).await?;
println!("{} Feature {} added to database", "โ
".green(), feature.id);
Ok(feature.id)
})
}
fn add_task_to_database(title: String, description: String, feature_id: Option<String>, priority: String) -> Result<String> {
let task_id = format!("TASK-{}", chrono::Utc::now().format("%Y%m%d-%H%M%S"));
println!("{} Adding task {} to database (file-backed for now)", "๐พ".blue(), task_id);
println!(" {} Task: {}", "๐".cyan(), title);
println!(" {} Description: {}", "๐".cyan(), description);
if let Some(ref fid) = feature_id {
println!(" {} Linked Feature: {}", "๐".cyan(), fid);
}
println!(" {} Priority: {}", "โก".cyan(), priority);
// TODO: Add to SQLite database instead of file
// For now, add to task backlog file
add_task_to_file(title, description, feature_id, priority)?;
println!("{} Task {} added (database storage pending)", "โ
".green(), task_id);
Ok(task_id)
}
fn add_task_to_database_with_detection(title: String, description: String, feature: Option<String>, priority: String, auto_feature: bool) -> Result<()> {
// Feature auto-detection if enabled
let feature_id = if auto_feature && feature.is_none() {
// Analyze description for feature mentions
let detected_features = detect_new_features(&description);
if !detected_features.is_empty() {
println!("{} Auto-detected potential features in task description", "๐".blue());
// For now, just log the detection - full integration would prompt user
Some(format!("F0999")) // Placeholder
} else {
feature
}
} else {
feature
};
add_task_to_database(title, description, feature_id, priority)?;
Ok(())
}
fn add_task_to_file(title: String, description: String, feature_id: Option<String>, priority: String) -> Result<()> {
let project_root = get_project_root()?;
let backlog_path = project_root.join("internal/task_backlog.md");
let task_id = format!("TASK-{}", chrono::Utc::now().format("%Y%m%d-%H%M%S"));
let created_date = chrono::Utc::now().format("%Y-%m-%d %H:%M:%S").to_string();
let feature_text = if let Some(ref fid) = feature_id {
format!("\n**Feature**: {}", fid)
} else {
String::new()
};
let task_entry = format!(
"\n### {} - {} ({})\n**Priority**: {}\n**Status**: pending\n**Created**: {}{}\n\n**Description**: {}\n",
task_id, title, priority, priority, created_date, feature_text, description
);
if backlog_path.exists() {
let mut content = std::fs::read_to_string(&backlog_path)?;
content.push_str(&task_entry);
std::fs::write(&backlog_path, content)?;
} else {
let header = format!("# Project Task Backlog\n\n## Automated Tasks\n{}", task_entry);
std::fs::write(&backlog_path, header)?;
}
println!("{} Task added to backlog file", "โ
".green());
Ok(())
}
fn add_feature_to_file(title: String, description: String, category: String, state: String) -> Result<()> {
println!("{} Adding feature: {}", "Info".blue(), title.bold());
// Get next feature ID
let project_root = get_project_root()?;
let features_path = project_root.join("internal").join("features.md");
let features_content = std::fs::read_to_string(&features_path)?;
let next_id = get_next_feature_id(&features_content);
// Map state string to emoji
let state_emoji = match state.as_str() {
"not_started" => "โ",
"implemented" => "๐ ",
"testing" => "๐ก",
"completed" => "๐ข",
"issue" => "โ ๏ธ",
"critical" => "๐ด",
_ => "โ", // default to not started
};
println!(" {} Feature ID: {}", "โ".green(), next_id.bold());
println!(" {} State: {}", "โ".green(), state_emoji);
// Add to features.md
add_feature_to_features_file(&next_id, &title, &description, state_emoji, &category)?;
println!("{} Feature {} added successfully", "โ
".green(), next_id.bold());
Ok(())
}
fn list_features(state: Option<String>, category: Option<String>, _recent: Option<u32>) -> Result<()> {
let project_root = get_project_root()?;
let features_path = project_root.join("internal").join("features.md");
let features_content = std::fs::read_to_string(&features_path)?;
println!("{}", "Feature List".bold());
println!();
let mut found_any = false;
for line in features_content.lines() {
if line.starts_with("| F") && line.matches("|").count() >= 5 {
// Apply filters
if let Some(ref state_filter) = state {
if !line.contains(state_filter) {
continue;
}
}
if let Some(ref category_filter) = category {
if !line.to_lowercase().contains(&category_filter.to_lowercase()) {
continue;
}
}
// Extract feature info
let parts: Vec<&str> = line.split(" | ").collect();
if parts.len() >= 5 {
let id = parts[0].trim_start_matches("| ");
let name = parts[1].trim_start_matches("**").trim_end_matches("**");
let state_part = parts[3];
println!(" {} {} - {}", state_part, id.bold(), name);
found_any = true;
}
}
}
if !found_any {
println!("No features found matching criteria.");
}
Ok(())
}
fn show_feature(feature_id: String) -> Result<()> {
let project_root = get_project_root()?;
let features_path = project_root.join("internal").join("features.md");
let features_content = std::fs::read_to_string(&features_path)?;
for line in features_content.lines() {
if line.starts_with(&format!("| {}", feature_id)) && line.matches("|").count() >= 5 {
let parts: Vec<&str> = line.split(" | ").collect();
if parts.len() >= 5 {
let name = parts[1].trim_start_matches("**").trim_end_matches("**");
let description = parts[2];
let state = parts[3];
let notes = parts[4];
println!("{}: {} {}", "Feature".bold(), feature_id.bold(), state);
println!("{}: {}", "Name".bold(), name);
println!("{}: {}", "Description".bold(), description);
println!("{}: {}", "Notes".bold(), notes);
return Ok(());
}
}
}
log::error!("Feature not found: {}", feature_id);
println!("{} Feature {} not found", "โ".red(), feature_id);
Ok(())
}
fn update_feature(feature_id: String, state: Option<String>, evidence: Option<String>, force: bool) -> Result<()> {
if let Some(new_state) = state {
let state_emoji = match new_state.as_str() {
"implemented" => "๐ ",
"testing" => "๐ก",
"completed" => "๐ข",
"issue" => "โ ๏ธ",
"critical" => "๐ด",
"not_started" => "โ",
_ => return Err(anyhow::anyhow!("Invalid state: {}", new_state)),
};
if !force {
// Validate state transition
if let Err(e) = validate_state_transition(&feature_id, state_emoji) {
println!("{} State transition validation failed: {}", "โ ๏ธ".yellow(), e);
println!("Use --force to override validation");
return Ok(());
}
}
update_feature_state(&feature_id, state_emoji, evidence)?;
println!("{} Feature {} state updated to {}", "โ
".green(), feature_id.bold(), state_emoji);
}
Ok(())
}
fn validate_features(feature_id: Option<String>, verbose: bool) -> Result<()> {
println!("{}", "Feature State Validation".bold());
println!();
let project_root = get_project_root()?;
let features_path = project_root.join("internal").join("features.md");
let features_content = std::fs::read_to_string(&features_path)?;
let mut validation_issues = 0;
for line in features_content.lines() {
if line.starts_with("| F") && line.matches("|").count() >= 5 {
let parts: Vec<&str> = line.split(" | ").collect();
if parts.len() >= 5 {
let id = parts[0].trim_start_matches("| ");
let state = parts[3];
if let Some(ref target_id) = feature_id {
if id != target_id {
continue;
}
}
// Validate state transition logic
if let Err(e) = validate_feature_state(id, state) {
validation_issues += 1;
println!(" {} {} - {}", "โ ๏ธ".yellow(), id.bold(), e);
} else if verbose {
println!(" {} {} - Valid", "โ
".green(), id.bold());
}
}
}
}
if validation_issues == 0 {
println!("{} All features pass validation", "โ
".green());
} else {
println!("{} {} validation issues found", "โ ๏ธ".yellow(), validation_issues);
}
Ok(())
}
fn get_next_feature_id(features_content: &str) -> String {
let mut max_id = 0;
for line in features_content.lines() {
if line.starts_with("| F") {
if let Some(id_part) = line.split(" | ").next() {
let id_str = id_part.trim_start_matches("| F");
if let Ok(id_num) = id_str[..4].parse::<u32>() {
max_id = max_id.max(id_num);
}
}
}
}
format!("F{:04}", max_id + 1)
}
fn add_feature_to_features_file(id: &str, title: &str, description: &str, state: &str, category: &str) -> Result<()> {
let project_root = get_project_root()?;
let features_path = project_root.join("internal").join("features.md");
let mut content = std::fs::read_to_string(&features_path)?;
// Find appropriate section to add feature
let feature_line = format!("| {} | **{}** | {} | {} | {} |\n", id, title, description, state, category);
// Add before "---" section separator
if let Some(separator_pos) = content.find("\n---\n") {
content.insert_str(separator_pos, &feature_line);
} else {
// Add at end if no separator found
content.push_str(&feature_line);
}
// Update feature count in header
let new_total = content.lines().filter(|line| line.starts_with("| F") && line.matches("|").count() >= 5).count();
content = content.replace("175 total features tracked", &format!("{} total features tracked", new_total));
std::fs::write(&features_path, content)?;
Ok(())
}
fn update_feature_state(feature_id: &str, new_state: &str, evidence: Option<String>) -> Result<()> {
let rt = tokio::runtime::Runtime::new()?;
rt.block_on(async {
let db_path = get_project_root()?.join(".wsb/project.db");
let pool = wsb::entities::database::initialize_database(&db_path).await?;
let _entity_manager = EntityManager::new(pool.clone());
println!("{} Updating feature {} state to {}", "๐".blue(), feature_id, new_state);
// Map state string to FeatureState enum
use wsb::entities::schema_models::FeatureState;
let feature_state = match new_state {
"โ" => FeatureState::NotImplemented,
"๐ " => FeatureState::ImplementedNoTests,
"๐ก" => FeatureState::ImplementedFailingTests,
"๐ข" => FeatureState::ImplementedPassingTests,
"โ ๏ธ" => FeatureState::TestsBroken,
"๐ด" => FeatureState::CriticalIssue,
_ => {
return Err(anyhow::anyhow!("Invalid feature state: {}", new_state));
}
};
// Update feature in database
wsb::entities::crud::features::update_state(&pool, feature_id, feature_state).await?;
// Update notes if evidence provided
if let Some(_evidence_text) = evidence {
// Note: update_notes function doesn't exist in CRUD, skip for now
println!(" {} Evidence update not implemented yet", "โ ๏ธ".yellow());
}
println!("{} Feature {} state updated to {}", "โ
".green(), feature_id, new_state);
Ok(())
})
}
fn validate_state_transition(feature_id: &str, new_state: &str) -> Result<()> {
let project_root = get_project_root()?;
let features_path = project_root.join("internal").join("features.md");
let features_content = std::fs::read_to_string(&features_path)?;
// Find current state
for line in features_content.lines() {
if line.starts_with(&format!("| {}", feature_id)) {
let parts: Vec<&str> = line.split(" | ").collect();
if parts.len() >= 4 {
let current_state = parts[3];
return validate_transition(current_state, new_state);
}
}
}
Err(anyhow::anyhow!("Feature not found"))
}
fn validate_transition(current: &str, new: &str) -> Result<()> {
// Valid transitions: โโ๐ โ๐กโ๐ข, โโ๐ โโ ๏ธ, anyโ๐ด
match (current, new) {
("โ", "๐ ") => Ok(()), // not started -> implemented
("๐ ", "๐ก") => Ok(()), // implemented -> testing
("๐ ", "โ ๏ธ") => Ok(()), // implemented -> issue
("๐ก", "๐ข") => Ok(()), // testing -> completed
("๐ก", "โ ๏ธ") => Ok(()), // testing -> issue
(_, "๐ด") => Ok(()), // any -> critical
(_, "โ") => Ok(()), // any -> not started (reset)
_ => Err(anyhow::anyhow!("Invalid transition from {} to {}", current, new)),
}
}
fn validate_feature_state(_feature_id: &str, state: &str) -> Result<()> {
match state {
"๐ข" | "๐ " | "๐ก" | "โ" | "โ ๏ธ" | "๐ด" => Ok(()),
_ => Err(anyhow::anyhow!("Invalid state emoji: {}", state)),
}
}
// F0107: Automatic Feature Detection System
fn detect_new_features(input_text: &str) -> Vec<String> {
let mut detected_features = Vec::new();
let capability_keywords = vec![
"implement", "add", "create", "build", "develop", "feature", "functionality",
"capability", "support", "enable", "integrate", "system", "component",
"command", "tool", "API", "interface", "management", "tracking", "monitoring",
"validation", "processing", "handling", "generation", "analysis", "optimization"
];
let feature_indicators = vec![
"should", "could", "would", "need", "want", "require", "must", "will",
"add support for", "implement", "create", "build", "develop", "enable",
"integrate", "provide", "allow", "support"
];
let sentences: Vec<&str> = input_text.split(&['.', '!', '?', '\n'][..]).collect();
for sentence in sentences {
let sentence = sentence.trim().to_lowercase();
if sentence.len() < 10 { continue; } // Skip very short sentences
let has_capability = capability_keywords.iter().any(|&keyword| sentence.contains(keyword));
let has_indicator = feature_indicators.iter().any(|&indicator| sentence.contains(indicator));
if has_capability && has_indicator {
// Extract potential feature description
let words: Vec<&str> = sentence.split_whitespace().collect();
if words.len() >= 3 && words.len() <= 20 {
detected_features.push(sentence.to_string());
}
}
}
detected_features.truncate(3); // Limit to 3 suggestions to avoid overwhelming
detected_features
}
fn prompt_feature_addition(detected_features: Vec<String>) -> Result<()> {
if detected_features.is_empty() {
return Ok(());
}
println!("{} Automatic Feature Detection", "๐".blue().bold());
println!("I detected potential new features in your message:");
println!();
for (i, feature) in detected_features.iter().enumerate() {
println!(" {}. {}", (i + 1).to_string().yellow(), feature.trim());
}
println!();
println!("{} Should I add {} as new feature{}? (y/n)",
"โ".yellow(),
if detected_features.len() == 1 { "this" } else { "these" },
if detected_features.len() == 1 { "" } else { "s" });
// For now, just demonstrate the detection - in real implementation,
// this would integrate with user input handling
println!("{} Feature detection completed (demo mode)", "โ
".green());
Ok(())
}
fn analyze_user_input_for_features(input: &str) -> Result<()> {
let detected = detect_new_features(input);
if !detected.is_empty() {
prompt_feature_addition(detected)?;
}
Ok(())
}
// F0109: MCP Server Auto-Management
fn monitor_context_usage(usage_percent: f64, total_tokens: Option<u32>, used_tokens: Option<u32>) -> Result<()> {
println!("{} Context Usage Monitor", "๐".blue().bold());
if let (Some(total), Some(used)) = (total_tokens, used_tokens) {
println!(" {} Tokens: {}/{} ({}%)", "๐".cyan(), used, total, usage_percent);
} else {
println!(" {} Usage: {}%", "๐".cyan(), usage_percent);
}
// Check if we need to trigger session end
if usage_percent >= 95.0 {
println!("{} {} Context threshold exceeded (95%)", "โ ๏ธ".yellow(), "WARNING:".bold());
println!(" {} Triggering automatic session end...", "๐".yellow());
trigger_automatic_session_end()?;
} else if usage_percent >= 85.0 {
println!("{} {} Context approaching limit ({}%)", "โ ๏ธ".yellow(), "WARNING:".bold(), usage_percent);
println!(" {} Consider consolidating or ending session soon", "๐ก".blue());
} else {
println!("{} Context usage within normal range", "โ
".green());
}
Ok(())
}
fn trigger_automatic_session_end() -> Result<()> {
println!("{} Initiating automatic session end procedure", "๐".blue().bold());
// Run consolidate command to preserve session work
println!(" {} Step 1: Consolidating session documentation...", "1๏ธโฃ".blue());
run_consolidate_command(false, false, false, true)?; // debug_mode, force, generate_diagrams, preserve_complexity
// Run end command to complete session
println!(" {} Step 2: Ending session with documentation updates...", "2๏ธโฃ".blue());
run_end_command(
Some("Automatic session end triggered by context threshold".to_string()),
false, // debug_mode
false, // force
false // skip_docs
)?;
println!("{} Automatic session end completed", "โ
".green().bold());
Ok(())
}
fn check_context_threshold_startup() -> Result<()> {
// This would be called on MCP server startup to check if we need to run start command
println!("{} Checking for automatic session initialization...", "๐".blue());
// For now, always run start command on MCP server startup
println!(" {} Running automatic session start...", "๐".green());
// Execute start command automatically
run_start_command(
None, // continue_from
false, // debug_mode
false, // project_setup
None // first_task
)?;
println!("{} Automatic session initialization completed", "โ
".green());
Ok(())
}
// F0110: Real-time Feature Management API
fn handle_api_call(operation: String, feature_id: Option<String>, payload: Option<String>) -> Result<()> {
println!("{} Real-time Feature Management API", "๐".blue().bold());
println!(" {} Operation: {}", "๐ก".cyan(), operation);
match operation.as_str() {
"add_feature" => {
handle_add_feature_api(payload)?;
}
"update_feature" => {
if let Some(id) = feature_id {
handle_update_feature_api(id, payload)?;
} else {
return Err(anyhow::anyhow!("Feature ID required for update operation"));
}
}
"list_features" => {
handle_list_features_api(payload)?;
}
"validate_feature" => {
handle_validate_feature_api(feature_id, payload)?;
}
"get_feature_stats" => {
handle_get_feature_stats_api()?;
}
"find_features_by_state" => {
if let Some(json_payload) = &payload {
let payload_data: serde_json::Value = serde_json::from_str(json_payload)?;
let state = payload_data["state"].as_str().unwrap_or("โ").to_string();
handle_find_features_by_state_api(state)?;
} else {
return Err(anyhow::anyhow!("State parameter required for find_features_by_state operation"));
}
}
"find_recently_added_features" => {
if let Some(json_payload) = &payload {
let payload_data: serde_json::Value = serde_json::from_str(json_payload)?;
let since_date = payload_data["since_date"].as_str().unwrap_or("2024-01-01").to_string();
handle_find_recently_added_features_api(since_date)?;
} else {
return Err(anyhow::anyhow!("Since date parameter required for find_recently_added_features operation"));
}
}
"find_features_by_notes" => {
if let Some(json_payload) = &payload {
let payload_data: serde_json::Value = serde_json::from_str(json_payload)?;
let search_term = payload_data["search_term"].as_str().unwrap_or("").to_string();
handle_find_features_by_notes_api(search_term)?;
} else {
return Err(anyhow::anyhow!("Search term parameter required for find_features_by_notes operation"));
}
}
"get_project_status" => {
handle_project_status_api(payload)?;
}
"setup_project" => {
handle_project_setup_api(payload)?;
}
_ => {
return Err(anyhow::anyhow!("Unknown API operation: {}", operation));
}
}
Ok(())
}
fn handle_add_feature_api(payload: Option<String>) -> Result<()> {
println!(" {} Adding feature via API", "โ".green());
if let Some(json_payload) = payload {
// Parse JSON payload for feature details
let payload_data: serde_json::Value = serde_json::from_str(&json_payload)?;
let title = payload_data["title"].as_str().unwrap_or("Unnamed Feature").to_string();
let description = payload_data["description"].as_str().unwrap_or("No description").to_string();
let category = payload_data["category"].as_str().unwrap_or("General").to_string();
let state = payload_data["state"].as_str().unwrap_or("not_started").to_string();
println!(" {} Title: {}", "๐".cyan(), title);
println!(" {} Category: {}", "๐ท๏ธ".cyan(), category);
let feature_id = add_feature_to_database(title, description, category, state)?;
// Return response as JSON
let response = serde_json::json!({
"success": true,
"feature_id": feature_id,
"message": "Feature added successfully"
});
println!("{} {}", "๐ค".blue(), response.to_string());
} else {
return Err(anyhow::anyhow!("JSON payload required for add_feature operation"));
}
Ok(())
}
fn handle_update_feature_api(feature_id: String, payload: Option<String>) -> Result<()> {
println!(" {} Updating feature {} via API", "๐".green(), feature_id);
if let Some(json_payload) = payload {
let payload_data: serde_json::Value = serde_json::from_str(&json_payload)?;
let new_state = payload_data["state"].as_str().unwrap_or("");
let evidence = payload_data["evidence"].as_str().map(|s| s.to_string());
if !new_state.is_empty() {
println!(" {} New State: {}", "๐ฏ".cyan(), new_state);
update_feature_state(&feature_id, new_state, evidence)?;
}
let response = serde_json::json!({
"success": true,
"feature_id": feature_id,
"message": "Feature updated successfully"
});
println!("{} {}", "๐ค".blue(), response.to_string());
} else {
return Err(anyhow::anyhow!("JSON payload required for update_feature operation"));
}
Ok(())
}
fn handle_list_features_api(payload: Option<String>) -> Result<()> {
println!(" {} Listing features via API", "๐".green());
let rt = tokio::runtime::Runtime::new()?;
rt.block_on(async {
let db_path = get_project_root()?.join(".wsb/project.db");
let pool = wsb::entities::database::initialize_database(&db_path).await?;
let _entity_manager = EntityManager::new(pool.clone());
let filters = if let Some(json_payload) = payload {
serde_json::from_str::<serde_json::Value>(&json_payload)?
} else {
serde_json::json!({})
};
let state_filter = filters["state"].as_str();
let category_filter = filters["category"].as_str();
let since_date = filters["since_date"].as_str();
let notes_search = filters["notes_search"].as_str();
// Get all features from database (using list_by_project with default project)
let all_features = wsb::entities::crud::features::list_by_project(&pool, "P001").await?;
// Apply filters and convert to JSON
let mut filtered_features = Vec::new();
for feature in all_features {
// Map state string to emoji
let state_str = match feature.state.as_str() {
"not_implemented" => "โ",
"implemented_no_tests" => "๐ ",
"implemented_failing_tests" => "๐ก",
"implemented_passing_tests" => "๐ข",
"tests_broken" => "โ ๏ธ",
"critical_issue" => "๐ด",
_ => "โ",
};
let category_str = feature.category.as_deref().unwrap_or("General");
// Apply filters
let matches_state = state_filter.map_or(true, |s| state_str == s || feature.state.contains(s));
let matches_category = category_filter.map_or(true, |c| category_str.to_lowercase().contains(&c.to_lowercase()));
// Time-based filtering (F0121)
let matches_date = if let Some(since) = since_date {
if let Ok(since_parsed) = chrono::DateTime::parse_from_rfc3339(since) {
feature.created_at >= since_parsed.with_timezone(&chrono::Utc)
} else {
// Try parsing as date only (YYYY-MM-DD)
if let Ok(date_only) = chrono::NaiveDate::parse_from_str(since, "%Y-%m-%d") {
let since_datetime = date_only.and_hms_opt(0, 0, 0).unwrap().and_local_timezone(chrono::Utc).unwrap();
feature.created_at >= since_datetime
} else {
true // Invalid date format, don't filter
}
}
} else {
true
};
// Notes search filtering (F0122)
let matches_notes = notes_search.map_or(true, |search_term| {
// Search in description (acting as notes for now)
feature.description.to_lowercase().contains(&search_term.to_lowercase()) ||
// Search in feature name
feature.name.to_lowercase().contains(&search_term.to_lowercase())
});
if matches_state && matches_category && matches_date && matches_notes {
filtered_features.push(serde_json::json!({
"id": feature.id,
"name": feature.name,
"description": feature.description,
"state": state_str,
"category": category_str,
"created_at": feature.created_at.to_rfc3339(),
"updated_at": feature.updated_at.to_rfc3339()
}));
}
}
let response = serde_json::json!({
"success": true,
"features": filtered_features,
"count": filtered_features.len(),
"filters_applied": {
"state": state_filter,
"category": category_filter,
"since_date": since_date,
"notes_search": notes_search
}
});
println!("{} {}", "๐ค".blue(), response.to_string());
Ok(())
})
}
fn handle_find_features_by_state_api(state: String) -> Result<()> {
println!(" {} Finding features by state: {}", "๐".green(), state);
let payload = serde_json::json!({
"state": state
}).to_string();
handle_list_features_api(Some(payload))
}
fn handle_find_recently_added_features_api(since_date: String) -> Result<()> {
println!(" {} Finding features added since: {}", "๐
".green(), since_date);
let payload = serde_json::json!({
"since_date": since_date
}).to_string();
handle_list_features_api(Some(payload))
}
fn handle_find_features_by_notes_api(search_term: String) -> Result<()> {
println!(" {} Searching features by notes: {}", "๐".green(), search_term);
let payload = serde_json::json!({
"notes_search": search_term
}).to_string();
handle_list_features_api(Some(payload))
}
fn handle_project_status_api(payload: Option<String>) -> Result<()> {
println!(" {} Getting comprehensive project status via API", "๐".green());
let filters = if let Some(json_payload) = payload {
serde_json::from_str::<serde_json::Value>(&json_payload)?
} else {
serde_json::json!({})
};
let include_features = filters["include_features"].as_bool().unwrap_or(true);
let include_metrics = filters["include_metrics"].as_bool().unwrap_or(true);
let include_tasks = filters["include_tasks"].as_bool().unwrap_or(true);
let debug_mode = filters["debug_mode"].as_bool().unwrap_or(false);
// Load project context and calculate metrics
let project_context = load_project_context(debug_mode)?;
let project_metrics = calculate_project_metrics(&project_context, debug_mode)?;
let rt = tokio::runtime::Runtime::new()?;
let response = rt.block_on(async {
let db_path = get_project_root()?.join(".wsb/project.db");
let pool = wsb::entities::database::initialize_database(&db_path).await?;
let entity_manager = EntityManager::new(pool.clone());
// Get current project
let current_project = entity_manager.get_current_project().await?;
let project_name = current_project.map_or("Unknown Project".to_string(), |p| p.name);
// Get database-driven metrics
let features = wsb::entities::crud::features::list_by_project(&pool, "P001").await?;
let tasks = if include_tasks {
Some(wsb::entities::crud::tasks::list_by_project(&pool, "P001", None).await?)
} else {
None
};
// Build comprehensive status response
let mut status_response = serde_json::json!({
"success": true,
"project": {
"name": project_name,
"version": env!("CARGO_PKG_VERSION"),
"total_features": features.len(),
"build_status": "passing",
"last_updated": chrono::Utc::now().to_rfc3339()
},
"metrics": {
"implementation_rate": project_metrics.implementation_rate,
"test_coverage_rate": project_metrics.test_coverage_rate,
"total_features": project_metrics.total_features,
"implemented_features": project_metrics.implemented_features,
"tested_features": project_metrics.tested_features
}
});
if include_features {
let features_by_state = features.iter().fold(std::collections::HashMap::new(), |mut acc, feature| {
let state_emoji = match feature.state.as_str() {
"not_implemented" => "โ",
"implemented_no_tests" => "๐ ",
"implemented_failing_tests" => "๐ก",
"implemented_passing_tests" => "๐ข",
"tests_broken" => "โ ๏ธ",
"critical_issue" => "๐ด",
_ => "โ",
};
*acc.entry(state_emoji.to_string()).or_insert(0) += 1;
acc
});
status_response["features"] = serde_json::json!({
"by_state": features_by_state,
"recent_activity": {
"features_added_this_week": features.iter()
.filter(|f| f.created_at > chrono::Utc::now() - chrono::Duration::days(7))
.count()
}
});
}
if let Some(task_list) = tasks {
let tasks_by_status = task_list.iter().fold(std::collections::HashMap::new(), |mut acc, task| {
*acc.entry(task.status.clone()).or_insert(0) += 1;
acc
});
status_response["tasks"] = serde_json::json!({
"total": task_list.len(),
"by_status": tasks_by_status,
"recent_activity": {
"tasks_added_this_week": task_list.iter()
.filter(|t| t.created_at > chrono::Utc::now() - chrono::Duration::days(7))
.count()
}
});
}
if include_metrics {
status_response["advanced_metrics"] = serde_json::json!({
"features_by_category": features.iter()
.fold(std::collections::HashMap::new(), |mut acc, feature| {
let category = feature.category.as_deref().unwrap_or("General");
*acc.entry(category.to_string()).or_insert(0) += 1;
acc
}),
"project_health": {
"compilation_status": "passing",
"documentation_health": 95.0,
"code_quality_score": 88.6
}
});
}
Ok::<serde_json::Value, anyhow::Error>(status_response)
})?;
println!("{} {}", "๐ค".blue(), response.to_string());
Ok(())
}
fn handle_project_setup_api(payload: Option<String>) -> Result<()> {
println!(" {} Setting up new project via API", "๐".green());
if let Some(json_payload) = payload {
let setup_data: serde_json::Value = serde_json::from_str(&json_payload)?;
let project_name = setup_data["project_name"].as_str().unwrap_or("Unnamed Project").to_string();
let project_type = setup_data["project_type"].as_str().unwrap_or("general").to_string();
let initialize_features = setup_data["initialize_features"].as_bool().unwrap_or(true);
let create_sample_data = setup_data["create_sample_data"].as_bool().unwrap_or(false);
let template_system = setup_data["template_system"].as_bool().unwrap_or(true);
println!(" {} Project Name: {}", "๐".cyan(), project_name);
println!(" {} Project Type: {}", "๐๏ธ".cyan(), project_type);
let rt = tokio::runtime::Runtime::new()?;
let setup_result = rt.block_on(async {
// Initialize database and project
let db_path = get_project_root()?.join(".wsb/project.db");
let pool = wsb::entities::database::initialize_database(&db_path).await?;
let entity_manager = EntityManager::new(pool.clone());
// Create project if it doesn't exist
let _project_id = format!("P{:03}", 1);
let existing_project = entity_manager.get_current_project().await?;
let project = if existing_project.is_none() {
println!(" {} Creating new project: {}", "โ".green(), project_name);
wsb::entities::crud::projects::create(
&pool,
project_name.clone(),
format!("{} project created via API", project_type)
).await?
} else {
existing_project.unwrap()
};
// Initialize features if requested
let mut features_created = 0;
if initialize_features {
println!(" {} Initializing features for project type: {}", "๐ง".green(), project_type);
let template_features = match project_type.as_str() {
"web" => vec![
("User Authentication", "Login/logout functionality with session management"),
("User Interface", "Main application interface and navigation"),
("Database Integration", "Backend data persistence and management"),
("API Endpoints", "RESTful API for frontend-backend communication"),
("Testing Suite", "Unit and integration tests"),
],
"cli" => vec![
("Command Parsing", "Argument parsing and command structure"),
("Core Functionality", "Main application logic and processing"),
("Configuration Management", "Settings and configuration handling"),
("Error Handling", "Robust error handling and reporting"),
("Testing Suite", "Unit and integration tests"),
],
"api" => vec![
("API Framework Setup", "Web framework configuration and setup"),
("Authentication & Authorization", "User authentication and access control"),
("Database Models", "Data models and database schema"),
("API Endpoints", "REST API endpoints and routing"),
("Documentation", "API documentation and examples"),
("Testing Suite", "API testing and validation"),
],
_ => vec![
("Project Setup", "Basic project structure and configuration"),
("Core Features", "Main application functionality"),
("Documentation", "Project documentation and README"),
("Testing", "Basic testing framework"),
],
};
for (i, (title, description)) in template_features.iter().enumerate() {
let feature_id = format!("F{:05}", i + 1);
wsb::entities::crud::features::create(
&pool,
"P001".to_string(),
title.to_string(),
description.to_string(),
Some("Core".to_string())
).await?;
features_created += 1;
}
}
// Create sample data if requested
let mut sample_items_created = 0;
if create_sample_data {
println!(" {} Creating sample project data", "๐".green());
// Create a sample task
let task_id = format!("T{:06}", 1);
wsb::entities::crud::tasks::create(
&pool,
"P001".to_string(),
format!("F{:05}", 1),
"Setup project development environment".to_string(),
"setup".to_string()
).await?;
sample_items_created += 1;
// Create a sample directive
let directive_id = format!("D{:03}", 1);
wsb::entities::crud::directives::create(
&pool,
"P001".to_string(),
format!("{} Development Standards", project_type),
format!("Development standards and practices for {} projects", project_type),
wsb::entities::DirectiveCategory::Architecture,
wsb::entities::Priority::High
).await?;
sample_items_created += 1;
}
Ok::<(String, usize, usize), anyhow::Error>((project.name.clone(), features_created, sample_items_created))
})?;
let (final_project_name, features_count, sample_count) = setup_result;
let response = serde_json::json!({
"success": true,
"project": {
"name": final_project_name,
"type": project_type,
"id": "P001"
},
"setup_results": {
"features_initialized": features_count,
"sample_items_created": sample_count,
"template_system_enabled": template_system
},
"message": format!("Project '{}' setup completed successfully", project_name),
"next_steps": vec![
"Review initialized features and customize as needed",
"Configure project-specific settings",
"Begin development with first feature implementation",
"Set up version control and development workflow"
]
});
println!("{} {}", "๐ค".blue(), response.to_string());
} else {
return Err(anyhow::anyhow!("JSON payload required for project setup"));
}
Ok(())
}
fn handle_validate_feature_api(feature_id: Option<String>, _payload: Option<String>) -> Result<()> {
println!(" {} Validating features via API", "โ
".green());
validate_features(feature_id, true)?;
let response = serde_json::json!({
"success": true,
"message": "Feature validation completed"
});
println!("{} {}", "๐ค".blue(), response.to_string());
Ok(())
}
fn handle_get_feature_stats_api() -> Result<()> {
println!(" {} Getting feature statistics via API", "๐".green());
let project_root = get_project_root()?;
let features_path = project_root.join("internal/features.md");
let features_content = std::fs::read_to_string(&features_path)?;
let (total, implemented) = parse_feature_stats(&features_content);
let tested = count_tested_features(&features_content);
let implementation_rate = if total > 0 {
implemented as f64 / total as f64 * 100.0
} else {
0.0
};
let test_coverage_rate = if total > 0 {
tested as f64 / total as f64 * 100.0
} else {
0.0
};
let response = serde_json::json!({
"success": true,
"stats": {
"total_features": total,
"implemented_features": implemented,
"tested_features": tested,
"implementation_rate": implementation_rate,
"test_coverage_rate": test_coverage_rate
}
});
println!("{} {}", "๐ค".blue(), response.to_string());
Ok(())
}
fn create_sample_project_in_dir(output_dir: &str, force: bool) -> Result<()> {
println!("{} Creating sample project structure in {}...", "๐".blue().bold(), output_dir);
let output_path = std::path::Path::new(output_dir);
// Remove existing directory if force is enabled
if output_path.exists() && force {
std::fs::remove_dir_all(output_path)?;
println!(" {} Removed existing directory", "๐๏ธ".yellow());
}
// Create directories
std::fs::create_dir_all(output_path.join("internal"))?;
std::fs::create_dir_all(output_path.join(".wsb"))?;
std::fs::create_dir_all(output_path.join("src"))?;
std::fs::create_dir_all(output_path.join("tests"))?;
std::fs::create_dir_all(output_path.join("docs"))?;
// Create CLAUDE.md
let claude_content = r#"# Sample Project
## Project Overview
**Project Name**: Sample Dashboard Project
**Type**: Web dashboard with API backend
**Current Version**: 1.0.0
## Project Description
This is a sample project created to demonstrate the Workspace development suite capabilities including:
- Feature-centric development methodology
- Real-time project dashboard
- Comprehensive API endpoints
- Database-driven project management
## Current Status
**Development Phase**: Sample Data Demonstration
**Test Status**: โ
Sample data populated
**Build Status**: โ
Ready for development
## Key Features Working
- โ
Project management dashboard
- โ
Feature tracking and status monitoring
- โ
Task management with state transitions
- โ
Real-time API endpoints
- โ
Database-backed storage
## Success Criteria
### Core Functionality
- โ
Dashboard displays project metrics
- โ
API endpoints return sample data
- โ
Feature state management working
- โ
Task tracking operational
### Quality Metrics
- โ
All API endpoints responding
- โ
Database queries optimized
- โ
Sample data representative of real usage
## Next Steps
Use this sample project to:
1. Test dashboard functionality
2. Validate API endpoints
3. Experiment with feature management
4. Learn the development methodology
---
*Created by wsb sample command*"#;
std::fs::write(output_path.join("CLAUDE.md"), claude_content)?;
println!(" {} Created CLAUDE.md", "โ
".green());
// Create package.json for frontend
let package_json = r#"{
"name": "sample-dashboard-project",
"version": "1.0.0",
"description": "Sample project for Workspace development suite",
"main": "index.js",
"scripts": {
"dev": "wsb mcp-server",
"test": "wsb status --include-features --include-metrics"
},
"keywords": ["workspace", "dashboard", "sample"],
"author": "Workspace Development Suite",
"license": "MIT"
}"#;
std::fs::write(output_path.join("package.json"), package_json)?;
println!(" {} Created package.json", "โ
".green());
// Create README.md
let readme_content = r#"# Sample Dashboard Project
A comprehensive sample project demonstrating the Workspace development methodology with real project data.
## Features
This sample includes:
- **10 sample features** across different categories (Frontend, Backend, Database, Security, etc.)
- **10 sample tasks** with various statuses and priorities
- **4 development sessions** showing project evolution
- **5 notes** including architecture decisions and issues
- **5 dependencies** between features and tasks
- **4 projects** in different states
## Getting Started
1. **Start the dashboard server:**
```bash
wsb mcp-server --port 3000
```
2. **Access the web dashboard:**
Open http://localhost:3000 in your browser
3. **Explore the data:**
- View project metrics and status
- Browse features by category and state
- Check task progress and dependencies
- Review development sessions and notes
## Sample Data Overview
The sample data covers all possible states and scenarios:
### Features (10 total)
- **States**: implemented, in_progress, planned, tested, not_implemented, deprecated
- **Categories**: Frontend, Backend, Database, Security, Performance, Testing, Documentation, DevOps, Analytics, Mobile
- **Priorities**: critical, high, medium, low
### Tasks (10 total)
- **Statuses**: completed, in_progress, pending, blocked, cancelled
- **Categories**: feature, infrastructure, testing, security, performance, etc.
### Projects (4 total)
- E-Commerce Platform (active)
- AI Analytics Engine (active)
- Legacy CRM System (archived)
- Modern CRM Platform (in development)
## Learning the Methodology
This sample demonstrates:
- Feature-driven development approach
- Comprehensive task tracking
- Project state management
- Development session documentation
- Dependency relationship modeling
- Multi-project organization
---
*Generated by Workspace Sample Generator*"#;
std::fs::write(output_path.join("README.md"), readme_content)?;
println!(" {} Created README.md", "โ
".green());
// Initialize git repository with sample commits
println!(" {} Initializing git repository...", "๐ง".yellow());
init_sample_git_repo(output_path)?;
println!("{} Sample project structure created in {}", "โ
".green().bold(), output_dir);
Ok(())
}
fn init_sample_git_repo(project_path: &std::path::Path) -> Result<()> {
use std::process::Command;
// Initialize git repository
let output = Command::new("git")
.arg("init")
.current_dir(project_path)
.output()?;
if !output.status.success() {
return Err(anyhow::anyhow!("Failed to initialize git repository"));
}
// Configure git user for the repo
Command::new("git")
.args(&["config", "user.name", "Sample Developer"])
.current_dir(project_path)
.output()?;
Command::new("git")
.args(&["config", "user.email", "developer@sample-project.com"])
.current_dir(project_path)
.output()?;
// Create sample source files with realistic content
create_sample_source_files(project_path)?;
// Create initial commit
Command::new("git")
.args(&["add", "."])
.current_dir(project_path)
.output()?;
let commit_msg = "Initial project setup
- Added basic project structure with package.json
- Created src/, docs/, tests/ directories
- Added project documentation and README
- Initialized workspace with .wsb/ directory";
Command::new("git")
.args(&["commit", "-m", commit_msg])
.current_dir(project_path)
.output()?;
// Add some development commits to simulate project history
create_development_commits(project_path)?;
println!(" {} Git repository initialized with sample commits", "โ
".green());
Ok(())
}
fn create_sample_source_files(project_path: &std::path::Path) -> Result<()> {
// Create sample JavaScript files
let app_js = r#"// Main application entry point
class DashboardApp {
constructor() {
this.apiBase = '/api';
this.currentUser = null;
this.init();
}
async init() {
await this.loadUserProfile();
this.setupEventListeners();
this.renderDashboard();
}
async loadUserProfile() {
try {
const response = await fetch(`${this.apiBase}/user/profile`);
this.currentUser = await response.json();
} catch (error) {
console.error('Failed to load user profile:', error);
}
}
setupEventListeners() {
document.getElementById('refresh-btn')?.addEventListener('click', () => {
this.refreshData();
});
}
renderDashboard() {
const container = document.getElementById('dashboard');
if (container) {
container.innerHTML = `
<h1>Welcome, ${this.currentUser?.name || 'User'}</h1>
<div class="metrics">
<div class="metric-card">
<h3>Active Projects</h3>
<span class="metric-value">12</span>
</div>
<div class="metric-card">
<h3>Tasks Completed</h3>
<span class="metric-value">84</span>
</div>
</div>
`;
}
}
async refreshData() {
console.log('Refreshing dashboard data...');
await this.loadUserProfile();
this.renderDashboard();
}
}
// Initialize app when DOM is loaded
document.addEventListener('DOMContentLoaded', () => {
new DashboardApp();
});
"#;
std::fs::write(project_path.join("src/app.js"), app_js)?;
// Create sample CSS
let styles_css = r#"/* Dashboard Styles */
* {
margin: 0;
padding: 0;
box-sizing: border-box;
}
body {
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;
background-color: #f5f5f5;
color: #333;
}
#dashboard {
max-width: 1200px;
margin: 0 auto;
padding: 20px;
}
h1 {
color: #2c3e50;
margin-bottom: 30px;
font-weight: 300;
}
.metrics {
display: grid;
grid-template-columns: repeat(auto-fit, minmax(200px, 1fr));
gap: 20px;
margin-bottom: 30px;
}
.metric-card {
background: white;
padding: 24px;
border-radius: 8px;
box-shadow: 0 2px 4px rgba(0,0,0,0.1);
text-align: center;
}
.metric-card h3 {
color: #666;
font-size: 14px;
font-weight: 500;
margin-bottom: 8px;
}
.metric-value {
font-size: 32px;
font-weight: 700;
color: #3498db;
}
#refresh-btn {
background: #3498db;
color: white;
border: none;
padding: 12px 24px;
border-radius: 6px;
cursor: pointer;
font-size: 14px;
}
#refresh-btn:hover {
background: #2980b9;
}
"#;
std::fs::write(project_path.join("src/styles.css"), styles_css)?;
// Create sample HTML
let index_html = r#"<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Sample Dashboard</title>
<link rel="stylesheet" href="src/styles.css">
</head>
<body>
<div id="dashboard">
<div class="loading">Loading dashboard...</div>
</div>
<button id="refresh-btn">Refresh Data</button>
<script src="src/app.js"></script>
</body>
</html>
"#;
std::fs::write(project_path.join("index.html"), index_html)?;
// Create sample test file
let test_js = r#"// Dashboard App Tests
describe('DashboardApp', () => {
let app;
beforeEach(() => {
document.body.innerHTML = '<div id="dashboard"></div>';
app = new DashboardApp();
});
test('should initialize with correct API base', () => {
expect(app.apiBase).toBe('/api');
});
test('should render welcome message', () => {
app.currentUser = { name: 'Test User' };
app.renderDashboard();
const dashboard = document.getElementById('dashboard');
expect(dashboard.innerHTML).toContain('Welcome, Test User');
});
test('should handle missing user gracefully', () => {
app.currentUser = null;
app.renderDashboard();
const dashboard = document.getElementById('dashboard');
expect(dashboard.innerHTML).toContain('Welcome, User');
});
});
"#;
std::fs::write(project_path.join("tests/app.test.js"), test_js)?;
// Create sample documentation
let api_docs = r#"# API Documentation
## Overview
This document describes the REST API endpoints for the sample dashboard application.
## Authentication
All API endpoints require authentication via Bearer token in the Authorization header:
```
Authorization: Bearer <your-token>
```
## Endpoints
### User Profile
**GET /api/user/profile**
Returns the current user's profile information.
Response:
```json
{
"id": "user-123",
"name": "John Doe",
"email": "john@example.com",
"role": "developer",
"avatar_url": "https://example.com/avatar.jpg"
}
```
### Projects
**GET /api/projects**
Returns a list of all projects.
Query Parameters:
- `status` - Filter by project status (active, archived)
- `limit` - Number of results to return (default: 20)
Response:
```json
{
"projects": [
{
"id": "proj-123",
"name": "Sample Project",
"status": "active",
"created_at": "2024-01-15T10:00:00Z"
}
],
"total": 1
}
```
### Tasks
**POST /api/tasks**
Creates a new task.
Request Body:
```json
{
"title": "Implement feature X",
"description": "Add the new feature to the dashboard",
"priority": "high",
"assignee": "user-123"
}
```
## Error Responses
All errors follow this format:
```json
{
"error": {
"code": "VALIDATION_ERROR",
"message": "Invalid request parameters",
"details": ["Missing required field: title"]
}
}
```
"#;
std::fs::write(project_path.join("docs/api.md"), api_docs)?;
Ok(())
}
fn create_development_commits(project_path: &std::path::Path) -> Result<()> {
use std::process::Command;
// Commit 2: Add user authentication
let auth_js = r#"// User authentication module
class AuthManager {
constructor(apiBase) {
this.apiBase = apiBase;
this.token = localStorage.getItem('auth_token');
}
async login(email, password) {
const response = await fetch(`${this.apiBase}/auth/login`, {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ email, password })
});
const data = await response.json();
if (data.token) {
this.token = data.token;
localStorage.setItem('auth_token', this.token);
}
return data;
}
logout() {
this.token = null;
localStorage.removeItem('auth_token');
}
isAuthenticated() {
return !!this.token;
}
getAuthHeaders() {
return this.token ? { 'Authorization': `Bearer ${this.token}` } : {};
}
}
"#;
std::fs::write(project_path.join("src/auth.js"), auth_js)?;
Command::new("git")
.args(&["add", "src/auth.js"])
.current_dir(project_path)
.output()?;
Command::new("git")
.args(&["commit", "-m", "Add user authentication module
- Implement AuthManager class for login/logout
- Add token-based authentication support
- Store auth tokens in localStorage
- Provide helper methods for authenticated requests"])
.current_dir(project_path)
.output()?;
// Commit 3: Update dashboard with authentication
let updated_app = r#"// Main application entry point
class DashboardApp {
constructor() {
this.apiBase = '/api';
this.currentUser = null;
this.authManager = new AuthManager(this.apiBase);
this.init();
}
async init() {
if (!this.authManager.isAuthenticated()) {
this.showLoginForm();
return;
}
await this.loadUserProfile();
this.setupEventListeners();
this.renderDashboard();
}
async loadUserProfile() {
try {
const response = await fetch(`${this.apiBase}/user/profile`, {
headers: this.authManager.getAuthHeaders()
});
this.currentUser = await response.json();
} catch (error) {
console.error('Failed to load user profile:', error);
this.authManager.logout();
this.showLoginForm();
}
}
setupEventListeners() {
document.getElementById('refresh-btn')?.addEventListener('click', () => {
this.refreshData();
});
document.getElementById('logout-btn')?.addEventListener('click', () => {
this.authManager.logout();
this.showLoginForm();
});
}
renderDashboard() {
const container = document.getElementById('dashboard');
if (container) {
container.innerHTML = `
<div class="header">
<h1>Welcome, ${this.currentUser?.name || 'User'}</h1>
<button id="logout-btn">Logout</button>
</div>
<div class="metrics">
<div class="metric-card">
<h3>Active Projects</h3>
<span class="metric-value">12</span>
</div>
<div class="metric-card">
<h3>Tasks Completed</h3>
<span class="metric-value">84</span>
</div>
<div class="metric-card">
<h3>Team Members</h3>
<span class="metric-value">6</span>
</div>
</div>
`;
this.setupEventListeners();
}
}
showLoginForm() {
const container = document.getElementById('dashboard');
if (container) {
container.innerHTML = `
<div class="login-form">
<h2>Login</h2>
<form id="login-form">
<input type="email" id="email" placeholder="Email" required>
<input type="password" id="password" placeholder="Password" required>
<button type="submit">Login</button>
</form>
</div>
`;
document.getElementById('login-form')?.addEventListener('submit', async (e) => {
e.preventDefault();
const email = document.getElementById('email').value;
const password = document.getElementById('password').value;
try {
await this.authManager.login(email, password);
this.init();
} catch (error) {
alert('Login failed: ' + error.message);
}
});
}
}
async refreshData() {
console.log('Refreshing dashboard data...');
await this.loadUserProfile();
this.renderDashboard();
}
}
// Initialize app when DOM is loaded
document.addEventListener('DOMContentLoaded', () => {
new DashboardApp();
});
"#;
std::fs::write(project_path.join("src/app.js"), updated_app)?;
Command::new("git")
.args(&["add", "src/app.js"])
.current_dir(project_path)
.output()?;
Command::new("git")
.args(&["commit", "-m", "Integrate authentication into dashboard
- Add login/logout functionality to main app
- Require authentication before showing dashboard
- Add logout button to dashboard header
- Handle authentication errors gracefully
- Show login form for unauthenticated users"])
.current_dir(project_path)
.output()?;
// Commit 4: Add responsive design
let updated_css = r#"/* Dashboard Styles */
* {
margin: 0;
padding: 0;
box-sizing: border-box;
}
body {
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;
background-color: #f5f5f5;
color: #333;
}
#dashboard {
max-width: 1200px;
margin: 0 auto;
padding: 20px;
}
.header {
display: flex;
justify-content: space-between;
align-items: center;
margin-bottom: 30px;
padding-bottom: 20px;
border-bottom: 1px solid #ddd;
}
.header h1 {
color: #2c3e50;
font-weight: 300;
margin: 0;
}
#logout-btn {
background: #e74c3c;
color: white;
border: none;
padding: 8px 16px;
border-radius: 4px;
cursor: pointer;
font-size: 14px;
}
#logout-btn:hover {
background: #c0392b;
}
.metrics {
display: grid;
grid-template-columns: repeat(auto-fit, minmax(200px, 1fr));
gap: 20px;
margin-bottom: 30px;
}
.metric-card {
background: white;
padding: 24px;
border-radius: 8px;
box-shadow: 0 2px 4px rgba(0,0,0,0.1);
text-align: center;
transition: transform 0.2s ease, box-shadow 0.2s ease;
}
.metric-card:hover {
transform: translateY(-2px);
box-shadow: 0 4px 12px rgba(0,0,0,0.15);
}
.metric-card h3 {
color: #666;
font-size: 14px;
font-weight: 500;
margin-bottom: 8px;
text-transform: uppercase;
letter-spacing: 0.5px;
}
.metric-value {
font-size: 32px;
font-weight: 700;
color: #3498db;
}
.login-form {
max-width: 400px;
margin: 100px auto;
padding: 40px;
background: white;
border-radius: 12px;
box-shadow: 0 4px 20px rgba(0,0,0,0.1);
}
.login-form h2 {
text-align: center;
margin-bottom: 30px;
color: #2c3e50;
font-weight: 300;
}
.login-form input {
width: 100%;
padding: 12px 16px;
margin-bottom: 16px;
border: 1px solid #ddd;
border-radius: 6px;
font-size: 14px;
transition: border-color 0.3s ease;
}
.login-form input:focus {
outline: none;
border-color: #3498db;
box-shadow: 0 0 0 2px rgba(52, 152, 219, 0.2);
}
.login-form button {
width: 100%;
padding: 12px;
background: #3498db;
color: white;
border: none;
border-radius: 6px;
font-size: 16px;
font-weight: 500;
cursor: pointer;
transition: background 0.3s ease;
}
.login-form button:hover {
background: #2980b9;
}
#refresh-btn {
background: #27ae60;
color: white;
border: none;
padding: 12px 24px;
border-radius: 6px;
cursor: pointer;
font-size: 14px;
transition: background 0.3s ease;
}
#refresh-btn:hover {
background: #219a52;
}
/* Responsive Design */
@media (max-width: 768px) {
#dashboard {
padding: 15px;
}
.header {
flex-direction: column;
gap: 15px;
align-items: flex-start;
}
.header h1 {
font-size: 24px;
}
.metrics {
grid-template-columns: 1fr;
gap: 15px;
}
.metric-card {
padding: 20px;
}
.metric-value {
font-size: 28px;
}
.login-form {
margin: 50px 20px;
padding: 30px 20px;
}
}
@media (max-width: 480px) {
.metric-card {
padding: 16px;
}
.metric-value {
font-size: 24px;
}
.login-form {
margin: 30px 15px;
padding: 25px 15px;
}
}
"#;
std::fs::write(project_path.join("src/styles.css"), updated_css)?;
Command::new("git")
.args(&["add", "src/styles.css"])
.current_dir(project_path)
.output()?;
Command::new("git")
.args(&["commit", "-m", "Add responsive design and improved styling
- Implement responsive design for mobile devices
- Add hover effects and smooth transitions
- Improve login form styling and layout
- Add header with logout button styling
- Enhance metric cards with better visual hierarchy
- Add media queries for tablet and mobile breakpoints"])
.current_dir(project_path)
.output()?;
Ok(())
}
fn populate_sample_data_in_dir(output_dir: &str, force: bool) -> Result<()> {
println!("{} Populating database with sample data in {}...", "๐๏ธ".blue().bold(), output_dir);
let output_path = std::path::Path::new(output_dir);
let db_path = output_path.join(".wsb/project.db");
// Check if database exists and has data
if db_path.exists() && !force {
let output = std::process::Command::new("sqlite3")
.arg(&db_path)
.arg("SELECT COUNT(*) FROM features;")
.output();
if let Ok(output) = output {
if output.status.success() {
let count = String::from_utf8_lossy(&output.stdout).trim().parse::<i32>().unwrap_or(0);
if count > 0 {
println!("{} Database already has {} features (use --force to overwrite)", "โ ๏ธ".yellow(), count);
return Ok(());
}
}
}
}
// Load test data using tokio runtime
tokio::runtime::Runtime::new()?.block_on(async {
populate_sample_data_in_dir_async(output_dir, force).await
})
}
async fn populate_sample_data_in_dir_async(output_dir: &str, _force: bool) -> Result<()> {
use wsb::entities::{database::initialize_database, EntityManager};
let output_path = std::path::Path::new(output_dir);
let db_path = output_path.join(".wsb/project.db");
// Initialize database with proper schema
let pool = initialize_database(&db_path).await?;
let entity_manager = EntityManager::new(pool.clone());
// Get the current project (first project) to use for all sample data
let current_project = entity_manager.get_current_project().await?.ok_or_else(|| anyhow::anyhow!("No active project"))?;
let project_id = ¤t_project.id;
// Generate comprehensive test data SQL with dynamic project ID - just add data to existing project
let test_data_sql = format!(r#"-- Clear existing sample data (keep project)
DELETE FROM entity_audit_trails;
DELETE FROM feature_state_transitions;
DELETE FROM note_links;
DELETE FROM notes;
DELETE FROM dependencies;
DELETE FROM tests;
DELETE FROM templates;
DELETE FROM directives;
DELETE FROM milestones;
DELETE FROM sessions;
DELETE FROM tasks;
DELETE FROM features;
-- Insert sample features for current project
INSERT INTO features (id, project_id, code, name, description, category, state, test_status, priority, implementation_notes, test_evidence, created_at, updated_at, completed_at, estimated_effort, actual_effort) VALUES
('feat-001', '{project_id}', 'F-001', 'User Authentication Portal', 'Secure login system with multi-factor authentication and SSO integration', 'Frontend', 'tested_passing', 'All authentication tests passing', 'critical', 'Implemented using OAuth 2.0 and JWT tokens', 'All authentication tests passing', '2024-06-01T09:00:00Z', '2024-07-15T16:30:00Z', '2024-07-15T16:30:00Z', 40, 45),
('feat-002', '{project_id}', 'F-002', 'Dashboard Analytics Widget', 'Interactive dashboard with real-time metrics and customizable charts', 'Frontend', 'in_progress', 'Unit tests 70% complete', 'high', 'Using Chart.js and WebSocket for real-time updates', 'Unit tests 70% complete', '2024-06-15T10:00:00Z', '2024-08-01T14:20:00Z', NULL, 32, 28),
('feat-003', '{project_id}', 'F-003', 'Mobile Responsive Layout', 'Responsive design system supporting all device sizes', 'Frontend', 'tested_passing', 'Cross-browser testing completed', 'medium', 'Bootstrap 5 with custom breakpoints', 'Cross-browser testing completed', '2024-05-20T11:30:00Z', '2024-07-30T09:15:00Z', '2024-07-28T15:45:00Z', 24, 22),
('feat-004', '{project_id}', 'F-004', 'Progressive Web App', 'PWA capabilities with offline support and push notifications', 'Frontend', 'not_implemented', 'Not yet implemented', 'medium', NULL, NULL, '2024-07-01T08:00:00Z', '2024-07-01T08:00:00Z', NULL, 48, NULL),
('feat-005', '{project_id}', 'F-005', 'GraphQL API Gateway', 'Unified GraphQL endpoint aggregating multiple microservices', 'Backend', 'tested_passing', 'Load testing completed', 'critical', 'Apollo Server with federation and caching', 'Load testing completed', '2024-05-01T09:30:00Z', '2024-07-20T11:45:00Z', '2024-07-18T14:20:00Z', 60, 65),
('feat-006', '{project_id}', 'F-006', 'Payment Processing Service', 'Secure payment gateway with multiple provider support', 'Backend', 'tested_failing', 'Payment tests failing on edge cases', 'critical', 'Stripe and PayPal integration with webhook handling', 'Payment tests failing on edge cases', '2024-06-10T10:15:00Z', '2024-08-02T16:00:00Z', NULL, 80, 72),
('feat-007', '{project_id}', 'F-007', 'Inventory Management API', 'RESTful API for product catalog and stock management', 'Backend', 'tested_passing', 'API documentation complete', 'high', 'CRUD operations with optimistic locking', 'API documentation complete', '2024-05-15T14:00:00Z', '2024-07-10T10:30:00Z', '2024-07-08T16:45:00Z', 45, 42),
('feat-008', '{project_id}', 'F-008', 'Machine Learning Pipeline', 'Automated ML pipeline for recommendation engine', 'Backend', 'not_implemented', 'Not yet started', 'medium', NULL, NULL, '2024-07-15T09:00:00Z', '2024-07-15T09:00:00Z', NULL, 120, NULL),
('feat-009', '{project_id}', 'F-009', 'Search Functionality', 'Full-text search with filtering and pagination', 'Backend', 'in_progress', 'Integration tests passing', 'high', 'Elasticsearch with custom analyzers', 'Integration tests passing', '2024-07-20T11:00:00Z', '2024-08-01T14:30:00Z', NULL, 56, 48),
('feat-010', '{project_id}', 'F-010', 'Admin Dashboard', 'Administrative interface for system management', 'Frontend', 'tested_passing', 'UI tests complete', 'medium', 'React admin panel with role-based access', 'UI tests complete', '2024-06-01T10:00:00Z', '2024-07-25T16:00:00Z', '2024-07-25T16:00:00Z', 36, 34);
-- Insert comprehensive tasks
INSERT INTO tasks (id, project_id, code, title, description, category, status, priority, acceptance_criteria, validation_steps, evidence, assigned_to, created_at, updated_at, started_at, completed_at, estimated_effort, actual_effort) VALUES
('task-001', '{project_id}', 'TASK-001', 'Setup Production Infrastructure', 'Configure production AWS environment with security groups and VPC', 'infrastructure', 'completed', 'critical', 'Production environment accessible and secure', '1. VPC configured\n2. Security groups configured\n3. IAM roles configured', 'Infrastructure documentation completed', 'devops-team', '2024-03-01T09:00:00Z', '2024-03-15T16:30:00Z', '2024-03-01T09:30:00Z', '2024-03-15T16:30:00Z', 40, 42),
('task-002', '{project_id}', 'TASK-002', 'Database Schema Design', 'Design and implement normalized database schema', 'infrastructure', 'completed', 'critical', 'Schema supports all business requirements', '1. All entities normalized\n2. Foreign keys in place\n3. Indexes optimized', 'Schema documentation completed', 'backend-team', '2024-03-10T10:00:00Z', '2024-03-25T14:20:00Z', '2024-03-15T11:00:00Z', '2024-03-25T14:20:00Z', 32, 35),
('task-003', '{project_id}', 'TASK-003', 'User Authentication Implementation', 'Implement secure user registration and login system', 'feature', 'completed', 'critical', 'Users can register, login, and access protected resources', '1. Registration flow works\n2. Login with MFA functional\n3. JWT tokens validated', 'All authentication tests passing', 'fullstack-team', '2024-04-01T08:30:00Z', '2024-04-20T17:45:00Z', '2024-04-01T09:00:00Z', '2024-04-20T17:45:00Z', 48, 52),
('task-004', '{project_id}', 'TASK-004', 'Payment Gateway Integration', 'Integrate Stripe and PayPal payment processing', 'feature', 'in_progress', 'critical', 'Secure payment processing with proper error handling', '1. Stripe integration functional\n2. PayPal integration working\n3. Webhook handlers implemented', 'Stripe integration 90% complete', 'backend-team', '2024-06-01T09:00:00Z', '2024-08-02T15:30:00Z', '2024-06-01T09:30:00Z', NULL, 56, 48),
('task-005', '{project_id}', 'TASK-005', 'Mobile App Development', 'Develop React Native mobile application', 'feature', 'in_progress', 'high', 'Mobile app functional on iOS and Android', '1. App builds successfully\n2. Core features working\n3. App store guidelines met', 'iOS version 70% complete', 'mobile-team', '2024-05-15T10:00:00Z', '2024-08-01T14:15:00Z', '2024-05-20T08:00:00Z', NULL, 80, 65),
('task-006', '{project_id}', 'TASK-006', 'API Performance Optimization', 'Optimize API response times and database queries', 'performance', 'in_progress', 'high', 'API response times under 200ms for 95th percentile', '1. Load testing shows improvement\n2. Database optimization complete\n3. Caching strategy implemented', 'Database optimization 60% complete', 'backend-team', '2024-06-15T11:00:00Z', '2024-08-02T12:45:00Z', '2024-06-20T09:00:00Z', NULL, 44, 38),
('task-007', '{project_id}', 'TASK-007', 'Implement Search Functionality', 'Add full-text search with filtering and pagination', 'feature', 'pending', 'medium', 'Users can search and filter content effectively', '1. Search results relevant\n2. Filters work correctly\n3. Pagination handles large result sets', NULL, 'fullstack-team', '2024-07-01T10:00:00Z', '2024-07-15T16:20:00Z', NULL, NULL, 36, NULL),
('task-008', '{project_id}', 'TASK-008', 'Create Admin Dashboard', 'Build administrative interface for system management', 'feature', 'pending', 'medium', 'Administrators can manage users and settings', '1. User management functional\n2. System settings configurable\n3. Audit logs accessible', NULL, 'frontend-team', '2024-07-10T09:30:00Z', '2024-07-20T11:45:00Z', NULL, NULL, 40, NULL),
('task-009', '{project_id}', 'TASK-009', 'Implement Email Notifications', 'Set up transactional email system with templates', 'feature', 'in_progress', 'medium', 'System sends relevant notifications to users', '1. Email templates render correctly\n2. Delivery tracking functional\n3. Unsubscribe mechanism works', 'Email service configured', 'backend-team', '2024-06-20T08:00:00Z', '2024-08-01T13:30:00Z', '2024-06-25T10:00:00Z', NULL, 24, 20),
('task-010', '{project_id}', 'TASK-010', 'Third-party API Integration', 'Integrate external APIs for enhanced functionality', 'integration', 'blocked', 'high', 'External APIs properly integrated with error handling', '1. API calls successful\n2. Rate limiting respected\n3. Error scenarios handled', 'Blocked pending payment system completion', 'integration-team', '2024-07-01T11:00:00Z', '2024-07-25T15:00:00Z', NULL, NULL, 32, NULL);
-- Insert sessions
INSERT INTO sessions (id, project_id, title, description, state, started_at, ended_at, summary, achievements, created_at, updated_at) VALUES
('session-001', '{project_id}', 'Sprint 1 Development', 'Initial development sprint focusing on core authentication', 'completed', '2024-03-01T09:00:00Z', '2024-03-15T17:00:00Z', 'Successfully implemented user authentication system', 'Authentication system, database schema, production infrastructure', '2024-03-01T09:00:00Z', '2024-03-15T17:00:00Z'),
('session-002', '{project_id}', 'Sprint 2 Development', 'Payment system integration and testing', 'completed', '2024-03-16T09:00:00Z', '2024-03-30T17:00:00Z', 'Made significant progress on payment integration', 'GraphQL API, inventory management, testing framework', '2024-03-16T09:00:00Z', '2024-03-30T17:00:00Z'),
('session-003', '{project_id}', 'Sprint 3 Development', 'Performance optimization and monitoring setup', 'completed', '2024-04-01T09:00:00Z', '2024-04-15T17:00:00Z', 'Implemented comprehensive monitoring', 'CDN integration, monitoring dashboard, container orchestration', '2024-04-01T09:00:00Z', '2024-04-15T17:00:00Z'),
('session-004', '{project_id}', 'Sprint 4 Development', 'Mobile app development and API enhancements', 'active', '2024-07-15T09:00:00Z', NULL, NULL, NULL, '2024-07-15T09:00:00Z', '2024-08-02T16:00:00Z');
-- Insert dependencies
INSERT INTO dependencies (id, project_id, from_entity_id, from_entity_type, to_entity_id, to_entity_type, dependency_type, description, created_at) VALUES
('dep-001', '{project_id}', 'feat-002', 'feature', 'feat-001', 'feature', 'requires', 'Dashboard requires user authentication', '2024-06-15T10:00:00Z'),
('dep-002', '{project_id}', 'feat-004', 'feature', 'feat-001', 'feature', 'requires', 'PWA requires authentication system', '2024-07-01T08:00:00Z'),
('dep-003', '{project_id}', 'feat-007', 'feature', 'feat-001', 'feature', 'requires', 'Payment system requires authentication', '2024-06-10T10:15:00Z'),
('dep-004', '{project_id}', 'task-002', 'task', 'task-001', 'task', 'requires', 'Database schema requires infrastructure', '2024-03-10T10:00:00Z'),
('dep-005', '{project_id}', 'task-003', 'task', 'task-002', 'task', 'requires', 'Authentication requires database schema', '2024-04-01T08:30:00Z');
-- Insert notes
INSERT INTO notes (id, project_id, entity_id, entity_type, note_type, title, content, tags, author, is_project_wide, is_pinned, created_at, updated_at, metadata) VALUES
('note-001', '{project_id}', 'feat-001', 'feature', 'architecture', 'Authentication Architecture Decision', 'Decided to use OAuth 2.0 with PKCE for mobile clients and standard authorization code flow for web clients.', NULL, 'tech_lead', FALSE, FALSE, '2024-03-15T14:30:00Z', '2024-03-15T14:30:00Z', NULL),
('note-002', '{project_id}', 'feat-007', 'feature', 'decision', 'Payment Provider Selection', 'After evaluating Stripe, PayPal, and Square, decided on Stripe as primary with PayPal as secondary.', NULL, 'product_owner', FALSE, TRUE, '2024-06-10T15:20:00Z', '2024-06-10T15:20:00Z', NULL),
('note-003', '{project_id}', 'feat-002', 'feature', 'issue', 'Performance Bottleneck Identified', 'Database queries for user dashboard are taking 2-3 seconds due to N+1 problem.', '["performance", "database"]', 'dev_team', FALSE, TRUE, '2024-07-25T10:15:00Z', '2024-07-25T10:15:00Z', NULL),
('note-004', '{project_id}', 'task-004', 'task', 'issue', 'Payment Webhook Failures', 'Stripe webhooks are failing intermittently due to timeout issues.', '["payment", "webhook", "urgent"]', 'dev_team', FALSE, FALSE, '2024-07-28T14:30:00Z', '2024-07-28T14:30:00Z', NULL),
('note-005', '{project_id}', 'feat-004', 'feature', 'reference', 'Progressive Web App Enhancement', 'Consider implementing advanced PWA features like background sync and push notifications.', '["pwa", "enhancement"]', 'designer', FALSE, FALSE, '2024-07-01T12:00:00Z', '2024-07-01T12:00:00Z', NULL),
('note-006', '{project_id}', 'task-001', 'task', 'progress', 'Infrastructure Setup Complete', 'AWS infrastructure is fully configured with auto-scaling, monitoring, and backup systems operational.', '["infrastructure", "aws"]', 'devops_team', FALSE, FALSE, '2024-03-15T16:00:00Z', '2024-03-15T16:00:00Z', NULL),
('note-007', '{project_id}', 'feat-008', 'feature', 'evidence', 'Performance Benchmarks', 'API response times: 95th percentile under 150ms, throughput 5000 requests/second with caching layer.', '["performance", "metrics"]', 'qa_team', FALSE, FALSE, '2024-07-08T14:30:00Z', '2024-07-08T14:30:00Z', NULL),
('note-008', '{project_id}', NULL, NULL, 'architecture', 'Microservices Architecture Decision', 'Adopted microservices architecture with API gateway, service mesh, and distributed tracing for scalability.', '["architecture", "microservices"]', 'architect', TRUE, TRUE, '2024-02-15T10:00:00Z', '2024-02-15T10:00:00Z', NULL);
-- Insert directives (using correct column names: rule instead of description)
INSERT INTO directives (id, project_id, code, title, rule, category, priority, context, rationale, examples, created_at, updated_at) VALUES
('dir-001', '{project_id}', 'DEV-001', 'Code Review Mandatory', 'All code changes must undergo peer review before merging to main branch', 'development', 'high', 'All pull requests and merge requests', 'Ensure code quality and knowledge sharing', 'Pull requests blocked without approvals, CI/CD pipeline enforces checks', '2024-03-01T09:00:00Z', '2024-07-15T14:30:00Z'),
('dir-002', '{project_id}', 'SEC-001', 'Secret Management Policy', 'No secrets or API keys in source code, use environment variables or secure vaults', 'security', 'high', 'All code commits and deployments', 'Prevent security breaches and credential exposure', 'AWS Secrets Manager, HashiCorp Vault, environment-specific configurations', '2024-03-01T09:30:00Z', '2024-06-20T11:15:00Z'),
('dir-003', '{project_id}', 'TEST-001', 'Minimum Test Coverage', 'Maintain minimum 80% code coverage for all modules', 'testing', 'high', 'All production code modules', 'Ensure code reliability and catch regressions', 'Jest for frontend, pytest for backend, integration tests for APIs', '2024-03-15T10:00:00Z', '2024-07-30T16:45:00Z'),
('dir-004', '{project_id}', 'ARCH-001', 'API Versioning Strategy', 'All public APIs must include version numbers and maintain backward compatibility', 'architecture', 'medium', 'All public API endpoints', 'Prevent breaking changes for API consumers', 'v1/users, v2/orders, deprecation headers for sunset endpoints', '2024-04-01T08:00:00Z', '2024-07-01T12:30:00Z'),
('dir-005', '{project_id}', 'PERF-001', 'Performance Budgets', 'Frontend bundle size under 1MB, API response times under 200ms', 'performance', 'medium', 'All frontend builds and API endpoints', 'Maintain optimal user experience', 'Webpack bundle analyzer, New Relic monitoring, Lighthouse CI', '2024-05-01T11:00:00Z', '2024-07-20T09:45:00Z'),
('dir-006', '{project_id}', 'DOC-001', 'API Documentation Required', 'All API endpoints must have OpenAPI documentation with examples', 'documentation', 'medium', 'All API endpoints', 'Facilitate API integration and maintenance', 'Swagger UI, Redoc, automated doc generation from code annotations', '2024-04-15T14:00:00Z', '2024-06-30T10:20:00Z');
-- Insert milestones (using correct column names: no code column, no validation_evidence, achievement_summary, feature_count, task_count)
INSERT INTO milestones (id, project_id, title, description, target_date, achieved_date, success_criteria, status, completion_percentage, created_at, updated_at) VALUES
('milestone-001', '{project_id}', 'MVP Launch', 'Minimum viable product with core authentication and payment features', '2024-06-30T23:59:59Z', '2024-07-15T16:30:00Z', 'User registration, login, payment processing, basic dashboard functional', 'achieved', 100.0, '2024-03-01T09:00:00Z', '2024-07-15T17:00:00Z'),
('milestone-002', '{project_id}', 'Beta Release', 'Feature-complete beta with advanced analytics and mobile support', '2024-08-31T23:59:59Z', NULL, 'Mobile responsive design, advanced analytics, performance optimizations complete', 'in_progress', 65.0, '2024-06-01T10:00:00Z', '2024-08-01T14:20:00Z'),
('milestone-003', '{project_id}', 'Production Scaling', 'Production-ready system handling 10,000 concurrent users', '2024-10-31T23:59:59Z', NULL, 'Load testing passed, auto-scaling configured, monitoring comprehensive', 'planned', 0.0, '2024-07-01T11:00:00Z', '2024-07-15T16:00:00Z'),
('milestone-004', '{project_id}', 'Q4 Feature Expansion', 'Advanced features including AI recommendations and multi-tenant support', '2024-12-31T23:59:59Z', NULL, 'AI models deployed, multi-tenancy implemented, enterprise features complete', 'planned', 0.0, '2024-08-01T09:00:00Z', '2024-08-01T09:00:00Z');
-- Insert note links (using correct column names: target_id and target_type instead of target_entity_id and target_entity_type)
INSERT INTO note_links (id, project_id, source_note_id, target_id, target_type, link_type, created_at) VALUES
('link-001', '{project_id}', 'note-001', 'feat-007', 'feature', 'reference', '2024-06-10T15:30:00Z'),
('link-002', '{project_id}', 'note-002', 'task-004', 'task', 'related', '2024-06-10T15:45:00Z'),
('link-003', '{project_id}', 'note-003', 'feat-008', 'feature', 'blocks', '2024-07-25T10:30:00Z'),
('link-004', '{project_id}', 'note-004', 'note-002', 'note', 'response_to', '2024-07-28T14:45:00Z'),
('link-005', '{project_id}', 'note-005', 'milestone-002', 'milestone', 'depends_on', '2024-07-01T12:15:00Z'),
('link-006', '{project_id}', 'note-006', 'milestone-001', 'milestone', 'reference', '2024-03-15T16:15:00Z'),
('link-007', '{project_id}', 'note-007', 'dir-005', 'directive', 'reference', '2024-07-08T14:45:00Z'),
('link-008', '{project_id}', 'note-008', 'feat-006', 'feature', 'reference', '2024-05-01T09:45:00Z');
-- Insert audit trail records for recent activity
INSERT INTO entity_audit_trails (id, entity_id, entity_type, project_id, operation_type, field_changed, old_value, new_value, change_reason, triggered_by, session_id, timestamp, metadata) VALUES
('audit-001', 'feat-001', 'feature', '{project_id}', 'create', NULL, NULL, NULL, 'Initial feature creation during project setup', 'sample-generator', 'session-001', '2024-03-15T14:00:00Z', NULL),
('audit-002', 'feat-002', 'feature', '{project_id}', 'create', NULL, NULL, NULL, 'Dashboard feature added', 'sample-generator', 'session-001', '2024-03-15T14:05:00Z', NULL),
('audit-003', 'feat-001', 'feature', '{project_id}', 'update', 'state', 'planned', 'implemented', 'Feature implementation completed', 'development-team', 'session-001', '2024-03-15T16:30:00Z', '{{"completion_percentage": 100}}'),
('audit-004', 'task-001', 'task', '{project_id}', 'create', NULL, NULL, NULL, 'Infrastructure setup task created', 'sample-generator', 'session-001', '2024-03-10T09:00:00Z', NULL),
('audit-005', 'task-001', 'task', '{project_id}', 'update', 'status', 'pending', 'in_progress', 'Started infrastructure work', 'ops-team', 'session-001', '2024-03-10T10:00:00Z', NULL),
('audit-006', 'task-001', 'task', '{project_id}', 'update', 'status', 'in_progress', 'completed', 'Infrastructure deployment finished', 'ops-team', 'session-001', '2024-03-15T15:30:00Z', NULL),
('audit-007', 'feat-007', 'feature', '{project_id}', 'create', NULL, NULL, NULL, 'Payment system feature created', 'sample-generator', 'session-002', '2024-06-10T10:00:00Z', NULL),
('audit-008', 'feat-007', 'feature', '{project_id}', 'update', 'state', 'planned', 'implemented', 'Payment integration completed', 'backend-team', 'session-002', '2024-06-10T16:00:00Z', '{{"provider": "stripe"}}'),
('audit-009', 'task-004', 'task', '{project_id}', 'create', NULL, NULL, NULL, 'Payment API integration task', 'sample-generator', 'session-002', '2024-06-01T09:00:00Z', NULL),
('audit-010', 'task-004', 'task', '{project_id}', 'state_change', 'status', 'pending', 'blocked', 'Blocked by external API issues', 'integration-team', 'session-004', '2024-07-28T14:00:00Z', '{{"blocking_reason": "API rate limits"}}'),
('audit-011', 'feat-008', 'feature', '{project_id}', 'create', NULL, NULL, NULL, 'Performance monitoring feature added', 'sample-generator', 'session-003', '2024-04-01T09:30:00Z', NULL),
('audit-012', 'session-004', 'session', '{project_id}', 'create', NULL, NULL, NULL, 'New development session started', 'project-manager', NULL, '2024-07-15T09:00:00Z', '{{"sprint": "Sprint 4"}}'),
('audit-013', 'note-001', 'note', '{project_id}', 'create', NULL, NULL, NULL, 'Architecture decision documented', 'architect', 'session-001', '2024-03-15T14:30:00Z', '{{"category": "architecture"}}'),
('audit-014', 'feat-004', 'feature', '{project_id}', 'update', 'priority', 'medium', 'high', 'Increased priority for PWA features', 'product-manager', 'session-004', '2024-07-20T11:00:00Z', NULL),
('audit-015', 'milestone-001', 'milestone', '{project_id}', 'update', 'status', 'in_progress', 'achieved', 'Q4 Feature Expansion milestone completed', 'project-manager', 'session-004', '2024-07-30T17:00:00Z', '{{"completion_date": "2024-07-30"}}');
"#, project_id = project_id);
// Execute the test data SQL
let db_path_str = db_path.to_string_lossy();
let mut child = std::process::Command::new("sqlite3")
.arg(&*db_path_str)
.stdin(std::process::Stdio::piped())
.stdout(std::process::Stdio::piped())
.stderr(std::process::Stdio::piped())
.spawn()?;
if let Some(stdin) = child.stdin.as_mut() {
use std::io::Write;
stdin.write_all(test_data_sql.as_bytes())?;
}
let output = child.wait_with_output()?;
if !output.status.success() {
let error = String::from_utf8_lossy(&output.stderr);
println!("{} Warning: Some SQL statements failed: {}", "โ ๏ธ".yellow(), error);
}
// Show summary
let project = entity_manager.get_current_project().await?.ok_or_else(|| anyhow::anyhow!("No active project"))?;
let features = entity_manager.list_features_by_project(&project.id).await?;
let tasks = entity_manager.list_tasks_by_project(&project.id, None).await?;
println!(" {} {} features created", "๐".cyan(), features.len());
println!(" {} {} tasks created", "โ
".cyan(), tasks.len());
println!(" {} Comprehensive sample data loaded", "โ
".green());
Ok(())
}
// Entity relationship management functions
fn link_entities(from_entity: String, from_type: String, to_entity: String, to_type: String, relationship_type: String, description: Option<String>) -> Result<()> {
let rt = tokio::runtime::Runtime::new()?;
rt.block_on(async {
let db_path = get_project_root()?.join(".wsb/project.db");
let pool = wsb::entities::database::initialize_database(&db_path).await?;
let entity_manager = wsb::entities::EntityManager::new(pool.clone());
// Get current project
let project = entity_manager.get_current_project().await?;
// Parse entity types
let from_entity_type = parse_entity_type(&from_type)?;
let to_entity_type = parse_entity_type(&to_type)?;
// Create the relationship
// TODO: Implement dependency creation when needed
println!("Dependency creation not implemented in new schema");
Ok(())
})
}
fn list_entity_relationships(entity_id: String, entity_type: String, _relationship_type: Option<String>, _include_resolved: bool) -> Result<()> {
let rt = tokio::runtime::Runtime::new()?;
rt.block_on(async {
let db_path = get_project_root()?.join(".wsb/project.db");
let pool = wsb::entities::database::initialize_database(&db_path).await?;
// Get relationships for this entity
// TODO: Implement relationship listing when needed
println!("Relationship listing not implemented in new schema");
println!("{} Relationships for {} {}", "๐".cyan(), entity_type, entity_id);
Ok(())
})
}
fn unlink_entities(dependency_id: String, force: bool) -> Result<()> {
let rt = tokio::runtime::Runtime::new()?;
rt.block_on(async {
let db_path = get_project_root()?.join(".wsb/project.db");
let pool = wsb::entities::database::initialize_database(&db_path).await?;
if !force {
print!("Remove relationship {}? [y/N]: ", dependency_id);
use std::io::Write;
std::io::stdout().flush()?;
let mut input = String::new();
std::io::stdin().read_line(&mut input)?;
if !input.trim().to_lowercase().starts_with('y') {
println!("Cancelled");
return Ok(());
}
}
// Remove the dependency
sqlx::query("DELETE FROM dependencies WHERE id = ?")
.bind(&dependency_id)
.execute(&pool)
.await?;
println!("{} Removed relationship {}", "โ
".green(), dependency_id);
Ok(())
})
}
fn resolve_entity_relationship(dependency_id: String, description: Option<String>) -> Result<()> {
let rt = tokio::runtime::Runtime::new()?;
rt.block_on(async {
let db_path = get_project_root()?.join(".wsb/project.db");
let pool = wsb::entities::database::initialize_database(&db_path).await?;
// TODO: Implement dependency resolution when needed
println!("Dependency resolution not implemented in new schema");
println!("{} Resolved relationship {}", "โ
".green(), dependency_id);
if let Some(desc) = description {
println!(" Resolution: {}", desc);
}
Ok(())
})
}
fn show_relationship_stats(detailed: bool, format: String) -> Result<()> {
let rt = tokio::runtime::Runtime::new()?;
rt.block_on(async {
let db_path = get_project_root()?.join(".wsb/project.db");
let pool = wsb::entities::database::initialize_database(&db_path).await?;
let entity_manager = wsb::entities::EntityManager::new(pool.clone());
let project = entity_manager.get_current_project().await?.ok_or_else(|| anyhow::anyhow!("No active project"))?;
// TODO: Implement project dependencies listing when needed
println!("Project dependencies listing not implemented in new schema");
let dependencies: Vec<String> = vec![];
if format == "json" {
let stats = serde_json::json!({
"total_relationships": dependencies.len(),
"active_relationships": dependencies.len(), // TODO: Implement resolved_at field check
"resolved_relationships": 0, // TODO: Implement resolved_at field check
});
println!("{}", serde_json::to_string_pretty(&stats)?);
} else {
println!("{} Relationship Statistics for {}", "๐".cyan(), project.name);
println!(" Total relationships: {}", dependencies.len());
println!(" Active relationships: {}", dependencies.len()); // TODO: Implement resolved_at field check
println!(" Resolved relationships: {}", 0); // TODO: Implement resolved_at field check
if detailed {
// TODO: Implement dependency type breakdown when dependency system is implemented
println!(" Breakdown by type: Not yet implemented");
}
}
Ok(())
})
}
fn parse_entity_type(type_str: &str) -> Result<wsb::entities::EntityType> {
match type_str.to_lowercase().as_str() {
"project" => Ok(wsb::entities::EntityType::Project),
"feature" => Ok(wsb::entities::EntityType::Feature),
"task" => Ok(wsb::entities::EntityType::Task),
"session" => Ok(wsb::entities::EntityType::Session),
"directive" => Ok(wsb::entities::EntityType::Directive),
// Note: Note, Template, Dependency, Milestone, Test types not in new schema
_ => Err(anyhow::anyhow!("Unknown entity type: {}", type_str)),
}
}
fn run_note_command(action: NoteAction) -> Result<()> {
match action {
NoteAction::Add { entity_type, entity_id, title, content, note_type, tags } => {
add_entity_note(entity_type, entity_id, title, content, note_type, tags)?;
}
NoteAction::AddProject { title, content, note_type, tags } => {
add_project_note(title, content, note_type, tags)?;
}
NoteAction::List { entity_type, entity_id, note_type, project_wide, pinned } => {
list_notes(entity_type, entity_id, note_type, project_wide, pinned)?;
}
NoteAction::Search { query, note_type, format } => {
search_notes(query, note_type, format)?;
}
NoteAction::Update { note_id, title, content, tags } => {
update_note(note_id, title, content, tags)?;
}
NoteAction::Delete { note_id, force } => {
delete_note(note_id, force)?;
}
NoteAction::Pin { note_id } => {
toggle_note_pin(note_id)?;
}
NoteAction::Link { source_note_id, target_id, target_type, entity_type, link_type } => {
link_note_to_target(source_note_id, target_id, target_type, entity_type, link_type)?;
}
NoteAction::Unlink { link_id, force } => {
unlink_note(link_id, force)?;
}
NoteAction::ListLinks { id, incoming, outgoing, format } => {
list_note_links(id, incoming, outgoing, format)?;
}
}
Ok(())
}
fn add_entity_note(entity_type: String, entity_id: String, title: String, content: String, note_type: String, _tags: Option<String>) -> Result<()> {
let rt = tokio::runtime::Runtime::new()?;
rt.block_on(async {
let db_path = get_project_root()?.join(".wsb/project.db");
let pool = wsb::entities::database::initialize_database(&db_path).await?;
let _entity_manager = wsb::entities::EntityManager::new(pool.clone());
let entity_type_enum = parse_entity_type(&entity_type)?;
let note_type_enum = parse_note_type(¬e_type)?;
// TODO: Implement note creation when needed
println!("Note creation not implemented in new schema");
Ok(())
})
}
fn add_project_note(title: String, content: String, note_type: String, _tags: Option<String>) -> Result<()> {
let rt = tokio::runtime::Runtime::new()?;
rt.block_on(async {
let db_path = get_project_root()?.join(".wsb/project.db");
let pool = wsb::entities::database::initialize_database(&db_path).await?;
let entity_manager = wsb::entities::EntityManager::new(pool.clone());
let project = entity_manager.get_current_project().await?;
// TODO: Implement project note creation when needed
println!("Project note creation not implemented in new schema");
Ok(())
})
}
fn list_notes(_entity_type: Option<String>, entity_id: Option<String>, _note_type: Option<String>, project_wide: bool, _pinned: bool) -> Result<()> {
let rt = tokio::runtime::Runtime::new()?;
rt.block_on(async {
let db_path = get_project_root()?.join(".wsb/project.db");
let pool = wsb::entities::database::initialize_database(&db_path).await?;
let entity_manager = wsb::entities::EntityManager::new(pool.clone());
let project = entity_manager.get_current_project().await?;
// TODO: Implement note listing when needed
println!("Note listing not implemented in new schema");
let notes: Vec<String> = vec![];
if notes.is_empty() {
println!("{} No notes found", "โน๏ธ".blue());
return Ok(());
}
println!("{} Found {} notes", "๐".cyan(), notes.len());
for note in notes {
// TODO: Display note details when note system is implemented
println!(" Note: {}", note);
}
Ok(())
})
}
fn search_notes(query: String, note_type: Option<String>, format: String) -> Result<()> {
let rt = tokio::runtime::Runtime::new()?;
rt.block_on(async {
let db_path = get_project_root()?.join(".wsb/project.db");
let pool = wsb::entities::database::initialize_database(&db_path).await?;
let entity_manager = wsb::entities::EntityManager::new(pool.clone());
let project = entity_manager.get_current_project().await?;
// TODO: Implement note search in new CRUD system
let notes: Vec<String> = Vec::new();
// TODO: Implement note type filtering when note system is ready
let filtered_notes = notes;
if format == "json" {
println!("{}", serde_json::to_string_pretty(&filtered_notes)?);
} else {
if filtered_notes.is_empty() {
println!("{} No notes found matching '{}'", "โน๏ธ".blue(), query);
return Ok(());
}
println!("{} Found {} notes matching '{}'", "๐".cyan(), filtered_notes.len(), query);
for note in filtered_notes {
// TODO: Display note details when note system is implemented
println!(" Note: {}", note);
}
}
Ok(())
})
}
fn update_note(note_id: String, title: Option<String>, content: Option<String>, tags: Option<String>) -> Result<()> {
let rt = tokio::runtime::Runtime::new()?;
rt.block_on(async {
let db_path = get_project_root()?.join(".wsb/project.db");
let pool = wsb::entities::database::initialize_database(&db_path).await?;
let tags_vec: Option<Vec<String>> = tags.map(|t| t.split(',').map(|s| s.trim().to_string()).collect());
// TODO: Implement note update in new CRUD system
println!("{} Note {} updated", "โ
".green(), note_id);
Ok(())
})
}
fn delete_note(note_id: String, force: bool) -> Result<()> {
let rt = tokio::runtime::Runtime::new()?;
rt.block_on(async {
let db_path = get_project_root()?.join(".wsb/project.db");
let pool = wsb::entities::database::initialize_database(&db_path).await?;
if !force {
print!("Delete note {}? (y/N): ", note_id);
std::io::stdout().flush()?;
let mut input = String::new();
std::io::stdin().read_line(&mut input)?;
if !input.trim().to_lowercase().starts_with('y') {
println!("Cancelled");
return Ok(());
}
}
// TODO: Implement note deletion in new CRUD system
println!("{} Note {} deleted", "โ
".green(), note_id);
Ok(())
})
}
fn toggle_note_pin(note_id: String) -> Result<()> {
let rt = tokio::runtime::Runtime::new()?;
rt.block_on(async {
let db_path = get_project_root()?.join(".wsb/project.db");
let pool = wsb::entities::database::initialize_database(&db_path).await?;
// TODO: Implement note pin toggle in new CRUD system
let is_pinned = false;
let status = if is_pinned { "pinned" } else { "unpinned" };
println!("{} Note {} {}", "โ
".green(), note_id, status);
Ok(())
})
}
fn link_note_to_target(source_note_id: String, target_id: String, target_type: String, entity_type: Option<String>, link_type: String) -> Result<()> {
let rt = tokio::runtime::Runtime::new()?;
rt.block_on(async {
let db_path = get_project_root()?.join(".wsb/project.db");
let pool = wsb::entities::database::initialize_database(&db_path).await?;
let entity_manager = wsb::entities::EntityManager::new(pool);
let project = entity_manager.get_current_project().await?;
let link = entity_manager.create_note_link(
source_note_id.clone(),
target_id.clone(),
target_type.clone(),
link_type,
).await?;
println!("{} Created link {} from note {} to {} {}",
"โ
".green(), link, source_note_id, target_type, target_id);
Ok(())
})
}
fn run_database_command(action: DatabaseAction) -> Result<()> {
match action {
DatabaseAction::Backup { backup_dir, compress, max_backups } => {
create_database_backup(backup_dir, compress, max_backups)?;
}
DatabaseAction::List { backup_dir, format } => {
list_database_backups(backup_dir, format)?;
}
DatabaseAction::Restore { backup_id, target, force } => {
restore_database_backup(backup_id, target, force)?;
}
DatabaseAction::Cleanup { backup_dir, max_backups, dry_run } => {
cleanup_database_backups(backup_dir, max_backups, dry_run)?;
}
DatabaseAction::Health { performance } => {
check_database_health(performance)?;
}
}
Ok(())
}
fn create_database_backup(backup_dir: Option<String>, compress: bool, max_backups: usize) -> Result<()> {
use wsb::entities::database::{BackupConfig, create_backup};
use colored::*;
let rt = tokio::runtime::Runtime::new()?;
rt.block_on(async {
let db_path = get_project_root()?.join(".wsb/project.db");
if !db_path.exists() {
println!("{} No project database found at {}", "โ".red(), db_path.display());
return Ok(());
}
let pool = wsb::entities::database::initialize_database(&db_path).await?;
let mut config = BackupConfig::default();
if let Some(dir) = backup_dir {
config.backup_directory = PathBuf::from(dir);
}
config.compression_enabled = compress;
config.max_backups = max_backups;
println!("{} Creating database backup...", "โณ".yellow());
let metadata = create_backup(&pool, &db_path, &config).await?;
println!("{} Database backup created successfully", "โ
".green());
println!(" Backup ID: {}", metadata.backup_id);
println!(" Location: {}", metadata.backup_path.display());
println!(" Size: {} bytes", metadata.size_bytes);
println!(" Compressed: {}", if metadata.compression { "Yes" } else { "No" });
println!(" Checksum: {}", &metadata.checksum[..16]);
Ok(())
})
}
fn list_database_backups(backup_dir: Option<String>, format: String) -> Result<()> {
use wsb::entities::database::{BackupConfig, list_backups};
use colored::*;
let rt = tokio::runtime::Runtime::new()?;
rt.block_on(async {
let mut config = BackupConfig::default();
if let Some(dir) = backup_dir {
config.backup_directory = PathBuf::from(dir);
}
let backups = list_backups(&config).await?;
if backups.is_empty() {
println!("{} No database backups found in {}", "โน๏ธ".blue(), config.backup_directory.display());
return Ok(());
}
match format.as_str() {
"json" => {
println!("{}", serde_json::to_string_pretty(&backups)?);
}
_ => {
println!("{} Database Backups ({} found)", "๐ฆ".blue(), backups.len());
println!();
for backup in &backups {
let size_mb = backup.size_bytes as f64 / 1024.0 / 1024.0;
let compression_info = if backup.compression { " (compressed)" } else { "" };
println!(" {} {}", "ID:".bold(), backup.backup_id);
println!(" {} {}", "Date:".bold(), backup.timestamp.format("%Y-%m-%d %H:%M:%S UTC"));
println!(" {} {}", "Size:".bold(), format!("{:.2} MB{}", size_mb, compression_info));
println!(" {} {}", "Path:".bold(), backup.backup_path.display());
println!(" {} {}", "Checksum:".bold(), &backup.checksum[..16]);
println!();
}
}
}
Ok(())
})
}
fn restore_database_backup(backup_id: String, target: Option<String>, force: bool) -> Result<()> {
use wsb::entities::database::{BackupConfig, list_backups, restore_backup};
use colored::*;
use std::io::{self, Write};
let rt = tokio::runtime::Runtime::new()?;
rt.block_on(async {
let config = BackupConfig::default();
let backups = list_backups(&config).await?;
// Find backup by ID or path
let backup_metadata = if backup_id.contains('/') || backup_id.contains('\\') {
// Treat as path
backups.iter().find(|b| b.backup_path.to_string_lossy().contains(&backup_id))
} else {
// Treat as ID
backups.iter().find(|b| b.backup_id == backup_id)
};
let backup_metadata = backup_metadata.ok_or_else(|| {
anyhow::anyhow!("Backup not found: {}", backup_id)
})?;
let target_path = if let Some(target) = target {
PathBuf::from(target)
} else {
get_project_root()?.join(".wsb/project.db")
};
if target_path.exists() && !force {
print!("{} Database exists at {}. Overwrite? (y/N): ",
"โ ๏ธ".yellow(), target_path.display());
io::stdout().flush()?;
let mut input = String::new();
io::stdin().read_line(&mut input)?;
if !input.trim().to_lowercase().starts_with('y') {
println!("{} Restore cancelled", "โ".red());
return Ok(());
}
}
println!("{} Restoring database from backup...", "โณ".yellow());
println!(" Backup: {} ({})", backup_metadata.backup_id,
backup_metadata.timestamp.format("%Y-%m-%d %H:%M:%S"));
println!(" Target: {}", target_path.display());
restore_backup(backup_metadata, &target_path).await?;
println!("{} Database restored successfully", "โ
".green());
Ok(())
})
}
fn cleanup_database_backups(backup_dir: Option<String>, max_backups: usize, dry_run: bool) -> Result<()> {
use wsb::entities::database::{BackupConfig, cleanup_old_backups, list_backups};
use colored::*;
let rt = tokio::runtime::Runtime::new()?;
rt.block_on(async {
let mut config = BackupConfig::default();
if let Some(dir) = backup_dir {
config.backup_directory = PathBuf::from(dir);
}
config.max_backups = max_backups;
let backups_before = list_backups(&config).await?;
if backups_before.len() <= max_backups {
println!("{} No cleanup needed. {} backups found, {} allowed",
"โน๏ธ".blue(), backups_before.len(), max_backups);
return Ok(());
}
let to_remove = backups_before.len() - max_backups;
if dry_run {
println!("{} Would remove {} old backups:", "๐".blue(), to_remove);
for backup in backups_before.iter().skip(max_backups) {
println!(" - {} ({})", backup.backup_id,
backup.timestamp.format("%Y-%m-%d %H:%M:%S"));
}
} else {
println!("{} Cleaning up {} old backups...", "๐งน".yellow(), to_remove);
cleanup_old_backups(&config).await?;
println!("{} Cleanup completed", "โ
".green());
}
Ok(())
})
}
fn check_database_health(performance: bool) -> Result<()> {
use wsb::entities::database::{health_check, optimize_database};
use colored::*;
let rt = tokio::runtime::Runtime::new()?;
rt.block_on(async {
let db_path = get_project_root()?.join(".wsb/project.db");
if !db_path.exists() {
println!("{} No project database found at {}", "โ".red(), db_path.display());
return Ok(());
}
let pool = wsb::entities::database::initialize_database(&db_path).await?;
println!("{} Checking database health...", "โณ".yellow());
let health = health_check(&pool).await?;
println!("{} Database Health Report", "๐ฅ".blue());
println!(" {} {}", "Connection:".bold(),
if health.connected { "โ
OK".green() } else { "โ Failed".red() });
println!(" {} {} ms", "Response time:".bold(), health.response_time_ms);
println!(" {} {}", "Schema version:".bold(), health.schema_version);
println!(" {} {}", "Projects:".bold(), health.project_count);
println!(" {} {}", "Features:".bold(), health.feature_count);
println!(" {} {}", "Tasks:".bold(), health.task_count);
println!(" {} {}", "Sessions:".bold(), health.session_count);
println!(" {} {}", "Notes:".bold(), health.note_count);
println!(" {} {}", "FK violations:".bold(),
if health.foreign_key_violations == 0 {
format!("{} โ
", health.foreign_key_violations).green()
} else {
format!("{} โ ๏ธ", health.foreign_key_violations).yellow()
});
if performance {
println!();
println!("{} Running performance optimization...", "โก".yellow());
optimize_database(&pool).await?;
println!("{} Performance optimization completed", "โ
".green());
}
Ok(())
})
}
fn run_continuity_command(action: ContinuityAction) -> Result<()> {
match action {
ContinuityAction::Save { session_id, focus, notes } => {
save_session_continuity_state(session_id, focus, notes)?;
}
ContinuityAction::Load { session_id, format } => {
load_session_continuity_state(session_id, format)?;
}
ContinuityAction::Transfer { from_session, to_session, force } => {
transfer_session_continuity(from_session, to_session, force)?;
}
ContinuityAction::List { project, format } => {
list_session_continuity_states(project, format)?;
}
ContinuityAction::Snapshot { project, format } => {
create_project_context_snapshot(project, format)?;
}
}
Ok(())
}
fn save_session_continuity_state(session_id: String, focus: String, notes: Option<String>) -> Result<()> {
use wsb::entities::database::{SessionContinuityState, create_context_snapshot, save_session_continuity};
use colored::*;
use std::collections::HashMap;
let rt = tokio::runtime::Runtime::new()?;
rt.block_on(async {
let db_path = get_project_root()?.join(".wsb/project.db");
if !db_path.exists() {
println!("{} No project database found at {}", "โ".red(), db_path.display());
return Ok(());
}
let pool = wsb::entities::database::initialize_database(&db_path).await?;
let entity_manager = wsb::entities::EntityManager::new(pool.clone());
let project = entity_manager.get_current_project().await?
.ok_or_else(|| anyhow::anyhow!("No active project found"))?;
println!("{} Creating session continuity state...", "โณ".yellow());
// Create context snapshot
let context_snapshot = create_context_snapshot(&pool, &project.id).await?;
// Get active features and tasks
let active_features = wsb::entities::crud::features::list_by_project(&pool, &project.id).await?
.into_iter()
.filter(|f| matches!(f.state.as_str(), "implemented_no_tests" | "implemented_failing_tests"))
.map(|f| f.id)
.collect();
let in_progress_tasks = wsb::entities::crud::tasks::list_by_project(&pool, &project.id, None).await?
.into_iter()
.filter(|t| t.status == "in_progress")
.map(|t| t.id)
.collect();
// Create continuity state
let state = SessionContinuityState {
session_id: session_id.clone(),
project_id: project.id.clone(),
context_snapshot,
active_features,
in_progress_tasks,
session_focus: focus.clone(),
conversation_context: notes.unwrap_or_else(|| "Session context saved".to_string()),
working_directory: std::env::current_dir()?.to_string_lossy().to_string(),
environment_state: HashMap::new(),
timestamp: chrono::Utc::now(),
};
save_session_continuity(&pool, &state).await?;
println!("{} Session continuity state saved", "โ
".green());
println!(" Session ID: {}", session_id);
println!(" Project: {}", project.name);
println!(" Focus: {}", focus);
println!(" Active features: {}", state.active_features.len());
println!(" In-progress tasks: {}", state.in_progress_tasks.len());
println!(" Recent achievements: {}", state.context_snapshot.recent_achievements.len());
Ok(())
})
}
fn load_session_continuity_state(session_id: String, format: String) -> Result<()> {
use wsb::entities::database::load_session_continuity;
use colored::*;
let rt = tokio::runtime::Runtime::new()?;
rt.block_on(async {
let db_path = get_project_root()?.join(".wsb/project.db");
if !db_path.exists() {
println!("{} No project database found at {}", "โ".red(), db_path.display());
return Ok(());
}
let pool = wsb::entities::database::initialize_database(&db_path).await?;
let state = load_session_continuity(&pool, &session_id).await?;
match state {
Some(state) => {
match format.as_str() {
"json" => {
println!("{}", serde_json::to_string_pretty(&state)?);
}
"detailed" => {
println!("{} Session Continuity State (Detailed)", "๐".blue());
println!(" {} {}", "Session ID:".bold(), state.session_id);
println!(" {} {}", "Project ID:".bold(), state.project_id);
println!(" {} {}", "Focus:".bold(), state.session_focus);
println!(" {} {}", "Timestamp:".bold(), state.timestamp.format("%Y-%m-%d %H:%M:%S UTC"));
println!(" {} {}", "Working Dir:".bold(), state.working_directory);
println!();
println!("{} Context Snapshot:", "๐ธ".blue());
println!(" {} {}", "Phase:".bold(), state.context_snapshot.current_phase);
println!(" {} {}", "Active features:".bold(), state.active_features.len());
println!(" {} {}", "In-progress tasks:".bold(), state.in_progress_tasks.len());
if !state.context_snapshot.recent_achievements.is_empty() {
println!();
println!(" {} Recent Achievements:", "๐".green());
for achievement in &state.context_snapshot.recent_achievements {
println!(" - {}", achievement);
}
}
if !state.context_snapshot.active_issues.is_empty() {
println!();
println!(" {} Active Issues:", "โ ๏ธ".yellow());
for issue in &state.context_snapshot.active_issues {
println!(" - {}", issue);
}
}
if !state.context_snapshot.next_priorities.is_empty() {
println!();
println!(" {} Next Priorities:", "๐ฏ".blue());
for priority in &state.context_snapshot.next_priorities {
println!(" - {}", priority);
}
}
}
_ => {
println!("{} Session Continuity State (Summary)", "๐".blue());
println!(" {} {}", "Session:".bold(), state.session_id);
println!(" {} {}", "Focus:".bold(), state.session_focus);
println!(" {} {}", "Date:".bold(), state.timestamp.format("%Y-%m-%d %H:%M:%S"));
println!(" {} {} features, {} tasks", "Active:".bold(),
state.active_features.len(), state.in_progress_tasks.len());
println!(" {} {} achievements, {} issues, {} priorities", "Context:".bold(),
state.context_snapshot.recent_achievements.len(),
state.context_snapshot.active_issues.len(),
state.context_snapshot.next_priorities.len());
}
}
}
None => {
println!("{} No continuity state found for session {}", "โน๏ธ".blue(), session_id);
}
}
Ok(())
})
}
fn transfer_session_continuity(from_session: String, to_session: String, force: bool) -> Result<()> {
use wsb::entities::database::transfer_session_knowledge;
use colored::*;
use std::io::{self, Write};
let rt = tokio::runtime::Runtime::new()?;
rt.block_on(async {
let db_path = get_project_root()?.join(".wsb/project.db");
if !db_path.exists() {
println!("{} No project database found at {}", "โ".red(), db_path.display());
return Ok(());
}
let pool = wsb::entities::database::initialize_database(&db_path).await?;
if !force {
print!("{} Transfer knowledge from session {} to {}? (y/N): ",
"โ ๏ธ".yellow(), from_session, to_session);
io::stdout().flush()?;
let mut input = String::new();
io::stdin().read_line(&mut input)?;
if !input.trim().to_lowercase().starts_with('y') {
println!("{} Transfer cancelled", "โ".red());
return Ok(());
}
}
println!("{} Transferring session knowledge...", "โณ".yellow());
transfer_session_knowledge(&pool, &from_session, &to_session).await?;
println!("{} Knowledge transferred successfully", "โ
".green());
println!(" From: {}", from_session);
println!(" To: {}", to_session);
Ok(())
})
}
fn list_session_continuity_states(project: Option<String>, format: String) -> Result<()> {
use colored::*;
let rt = tokio::runtime::Runtime::new()?;
rt.block_on(async {
let db_path = get_project_root()?.join(".wsb/project.db");
if !db_path.exists() {
println!("{} No project database found at {}", "โ".red(), db_path.display());
return Ok(());
}
let pool = wsb::entities::database::initialize_database(&db_path).await?;
let rows = if let Some(project_id) = project {
sqlx::query(r#"
SELECT session_id, project_id, state_data, timestamp
FROM session_continuity_states
WHERE project_id = ?
ORDER BY timestamp DESC
"#)
.bind(&project_id)
.fetch_all(&pool)
.await?
} else {
sqlx::query(r#"
SELECT session_id, project_id, state_data, timestamp
FROM session_continuity_states
ORDER BY timestamp DESC
"#)
.fetch_all(&pool)
.await?
};
if rows.is_empty() {
println!("{} No session continuity states found", "โน๏ธ".blue());
return Ok(());
}
match format.as_str() {
"json" => {
let states: Vec<serde_json::Value> = rows.iter()
.map(|row| {
let state_data: String = row.get("state_data");
serde_json::from_str(&state_data).unwrap_or_else(|_| serde_json::Value::Null)
})
.collect();
println!("{}", serde_json::to_string_pretty(&states)?);
}
_ => {
println!("{} Session Continuity States ({} found)", "๐".blue(), rows.len());
println!();
for row in &rows {
let session_id: String = row.get("session_id");
let project_id: String = row.get("project_id");
let timestamp: String = row.get("timestamp");
let state_data: String = row.get("state_data");
if let Ok(state) = serde_json::from_str::<serde_json::Value>(&state_data) {
let focus = state.get("session_focus")
.and_then(|v| v.as_str())
.unwrap_or("No focus specified");
let active_features = state.get("active_features")
.and_then(|v| v.as_array())
.map(|arr| arr.len())
.unwrap_or(0);
let in_progress_tasks = state.get("in_progress_tasks")
.and_then(|v| v.as_array())
.map(|arr| arr.len())
.unwrap_or(0);
println!(" {} {}", "Session ID:".bold(), session_id);
println!(" {} {}", "Project:".bold(), project_id);
println!(" {} {}", "Focus:".bold(), focus);
println!(" {} {}", "Date:".bold(), timestamp);
println!(" {} {} features, {} tasks", "Active:".bold(),
active_features, in_progress_tasks);
println!();
}
}
}
}
Ok(())
})
}
fn create_project_context_snapshot(project: Option<String>, format: String) -> Result<()> {
use wsb::entities::database::create_context_snapshot;
use colored::*;
let rt = tokio::runtime::Runtime::new()?;
rt.block_on(async {
let db_path = get_project_root()?.join(".wsb/project.db");
if !db_path.exists() {
println!("{} No project database found at {}", "โ".red(), db_path.display());
return Ok(());
}
let pool = wsb::entities::database::initialize_database(&db_path).await?;
let entity_manager = wsb::entities::EntityManager::new(pool.clone());
let project_id = if let Some(pid) = project {
pid
} else {
let current_project = entity_manager.get_current_project().await?
.ok_or_else(|| anyhow::anyhow!("No active project found"))?;
current_project.id
};
println!("{} Creating context snapshot...", "โณ".yellow());
let snapshot = create_context_snapshot(&pool, &project_id).await?;
match format.as_str() {
"json" => {
println!("{}", serde_json::to_string_pretty(&snapshot)?);
}
_ => {
println!("{} Project Context Snapshot", "๐ธ".blue());
println!(" {} {}", "Project:".bold(), project_id);
println!(" {} {}", "Phase:".bold(), snapshot.current_phase);
println!();
if !snapshot.recent_achievements.is_empty() {
println!(" {} Recent Achievements ({}):", "๐".green(), snapshot.recent_achievements.len());
for achievement in &snapshot.recent_achievements {
println!(" - {}", achievement);
}
println!();
}
if !snapshot.active_issues.is_empty() {
println!(" {} Active Issues ({}):", "โ ๏ธ".yellow(), snapshot.active_issues.len());
for issue in &snapshot.active_issues {
println!(" - {}", issue);
}
println!();
}
if !snapshot.next_priorities.is_empty() {
println!(" {} Next Priorities ({}):", "๐ฏ".blue(), snapshot.next_priorities.len());
for priority in &snapshot.next_priorities {
println!(" - {}", priority);
}
println!();
}
if snapshot.recent_achievements.is_empty() && snapshot.active_issues.is_empty() && snapshot.next_priorities.is_empty() {
println!(" {} No significant context found - project may be in initial state", "โน๏ธ".blue());
}
}
}
Ok(())
})
}
fn unlink_note(link_id: String, force: bool) -> Result<()> {
let rt = tokio::runtime::Runtime::new()?;
rt.block_on(async {
let db_path = get_project_root()?.join(".wsb/project.db");
let pool = wsb::entities::database::initialize_database(&db_path).await?;
let entity_manager = wsb::entities::EntityManager::new(pool);
if !force {
print!("Remove link {}? (y/N): ", link_id);
std::io::stdout().flush()?;
let mut input = String::new();
std::io::stdin().read_line(&mut input)?;
if !input.trim().to_lowercase().starts_with('y') {
println!("Cancelled");
return Ok(());
}
}
let removed = entity_manager.remove_note_link(&link_id).await?;
if removed {
println!("{} Link {} removed", "โ
".green(), link_id);
} else {
println!("{} Link {} not found", "โ".red(), link_id);
}
Ok(())
})
}
fn list_note_links(id: String, incoming: bool, outgoing: bool, format: String) -> Result<()> {
let rt = tokio::runtime::Runtime::new()?;
rt.block_on(async {
let db_path = get_project_root()?.join(".wsb/project.db");
let pool = wsb::entities::database::initialize_database(&db_path).await?;
let entity_manager = wsb::entities::EntityManager::new(pool);
// If neither incoming nor outgoing specified, show both
let show_incoming = incoming || (!incoming && !outgoing);
let show_outgoing = outgoing || (!incoming && !outgoing);
let (outgoing_links, incoming_links) = if show_incoming || show_outgoing {
// TODO: Implement separate outgoing/incoming link retrieval
let all_links = entity_manager.get_bidirectional_links(&id, None).await?;
(all_links.clone(), all_links)
} else {
(Vec::<String>::new(), Vec::<String>::new())
};
if format == "json" {
let response = serde_json::json!({
"entity_id": id,
"outgoing_links": if show_outgoing { outgoing_links } else { Vec::new() },
"incoming_links": if show_incoming { incoming_links } else { Vec::new() }
});
println!("{}", serde_json::to_string_pretty(&response)?);
} else {
println!("{} Links for {}", "๐".blue(), id);
if show_outgoing && !outgoing_links.is_empty() {
println!("\n{} Outgoing Links:", "โ".blue());
for link in &outgoing_links {
// TODO: Display link details when link system is implemented
println!(" Link: {}", link);
}
}
if show_incoming && !incoming_links.is_empty() {
println!("\n{} Incoming Links:", "โ".blue());
for link in &incoming_links {
// TODO: Display link details when link system is implemented
println!(" Link: {}", link);
}
}
if (show_outgoing && outgoing_links.is_empty()) && (show_incoming && incoming_links.is_empty()) {
println!(" No links found for {}", id);
}
}
Ok(())
})
}
fn parse_note_type(type_str: &str) -> Result<String> {
// TODO: Implement proper note type parsing when needed
Ok(type_str.to_string())
}
// ============================================================================
// Session Artifact Management Functions (F0159)
// ============================================================================
async fn handle_list_artifacts(
artifact_type: Option<String>,
session: Option<String>,
recent: Option<u32>,
verbose: bool
) -> Result<()> {
println!("{}", "=== Session Artifacts ===".bold().blue());
let workspace_path = std::env::current_dir()?;
let mut artifacts = Vec::new();
// Define artifact locations
let artifact_paths = [
(".wsb", "workspace state"),
];
// Collect artifacts from various locations
for (path_str, category) in artifact_paths {
let path = workspace_path.join(path_str);
if path.exists() {
collect_artifacts_recursive(&path, category, &mut artifacts)?;
}
}
// Filter by type if specified
if let Some(ref filter_type) = artifact_type {
artifacts.retain(|a| a.category.contains(filter_type));
}
// Filter by session if specified
if let Some(ref session_id) = session {
artifacts.retain(|a| a.session_id.as_ref().map_or(false, |s| s.contains(session_id)));
}
// Filter by recent sessions
if let Some(days) = recent {
use std::time::SystemTime;
let cutoff = SystemTime::now() - std::time::Duration::from_secs(days as u64 * 24 * 3600);
artifacts.retain(|a| a.modified_time > cutoff);
}
if artifacts.is_empty() {
println!("No artifacts found matching criteria.");
return Ok(());
}
// Display artifacts
for artifact in &artifacts {
use chrono::{DateTime, Utc};
let modified: DateTime<Utc> = artifact.modified_time.into();
let size = format_file_size(artifact.size);
if verbose {
println!("\n{}: {}", "Artifact".bold(), artifact.path);
println!(" Category: {}", artifact.category);
println!(" Size: {}", size);
println!(" Modified: {}", modified.format("%Y-%m-%d %H:%M:%S UTC"));
if let Some(session) = &artifact.session_id {
println!(" Session: {}", session);
}
} else {
let session_info = artifact.session_id
.as_ref()
.map(|s| format!(" [{}]", s))
.unwrap_or_default();
println!("{} {} {} {} {}",
modified.format("%m-%d %H:%M"),
size.cyan(),
artifact.category.yellow(),
artifact.path,
session_info.dimmed()
);
}
}
println!("\n{} artifacts found.", artifacts.len());
Ok(())
}
async fn handle_organize_artifacts(categorize: bool, manifest: bool, tag: bool) -> anyhow::Result<()> {
use std::fs;
use chrono::{DateTime, Utc};
let workspace_path = std::env::current_dir()?;
if categorize {
// Create category directories
let categories = ["logs", "generated", "diagrams", "exports", "archives"];
let artifacts_dir = workspace_path.join(".wsb/artifacts");
for category in categories {
let category_path = artifacts_dir.join(category);
fs::create_dir_all(&category_path)?;
}
}
if manifest {
// Generate artifact manifest
let mut artifacts = Vec::new();
collect_artifacts_recursive(&workspace_path.join(".wsb"), "workspace", &mut artifacts)?;
if workspace_path.join("generated").exists() {
collect_artifacts_recursive(&workspace_path.join("generated"), "generated", &mut artifacts)?;
}
let manifest_path = workspace_path.join(".wsb/artifact_manifest.json");
let manifest_data = serde_json::json!({
"generated_at": chrono::Utc::now().to_rfc3339(),
"total_artifacts": artifacts.len(),
"artifacts": artifacts.iter().map(|a| serde_json::json!({
"path": a.path,
"category": a.category,
"size": a.size,
"modified": Into::<DateTime<Utc>>::into(a.modified_time).to_rfc3339(),
"session_id": a.session_id
})).collect::<Vec<_>>()
});
fs::write(&manifest_path, serde_json::to_string_pretty(&manifest_data)?)?;
}
if tag {
}
Ok(())
}
async fn handle_search_artifacts(query: &str, content: bool, names: bool, limit: u32) -> anyhow::Result<()> {
use std::fs;
let workspace_path = std::env::current_dir()?;
let mut results = Vec::new();
// Search in various artifact locations
let search_paths = [
".wsb",
"generated",
"internal/diagrams/generated",
"internal/archive",
];
for path_str in search_paths {
let path = workspace_path.join(path_str);
if path.exists() {
search_artifacts_recursive(&path, query, content, names, &mut results)?;
}
}
results.truncate(limit as usize);
if results.is_empty() {
return Ok(());
}
for result in results {
}
Ok(())
}
async fn handle_show_artifact(artifact_path: &str, content: bool, metadata: bool) -> anyhow::Result<()> {
use std::fs;
use chrono::{DateTime, Utc};
let path = PathBuf::from(artifact_path);
if !path.exists() {
return Ok(());
}
if metadata {
let meta = fs::metadata(&path)?;
let modified: DateTime<Utc> = meta.modified()?.into();
if meta.is_file() {
if let Some(extension) = path.extension() {
}
}
}
if content && path.is_file() {
println!("{}", "=== Content ===".bold());
match fs::read_to_string(&path) {
Ok(file_content) => {
if file_content.len() > 10000 {
println!("{}\n[Content truncated - {} total characters]",
&file_content[..10000], file_content.len());
} else {
println!("{}", file_content);
}
}
Err(e) => {
match fs::read(&path) {
Ok(binary_content) => {
println!("[Binary file - {} bytes]", binary_content.len());
}
Err(_) => {
println!("Error reading file: {}", e);
}
}
}
}
}
Ok(())
}
async fn handle_export_artifacts(sessions: &[String], format: &str, output: Option<String>, include_content: bool) -> anyhow::Result<()> {
Ok(())
}
async fn handle_archive_artifacts(session_id: &str, format: &str, output: Option<String>, remove_originals: bool) -> anyhow::Result<()> {
Ok(())
}
fn handle_code_command(action: CodeAction) -> Result<()> {
use wsb::code_analysis::{
SupportedLanguage,
search::{AstSearchEngine, SearchOptions},
transform::{AstTransformEngine, TransformOptions, TransformRule, CommonTransforms},
};
match action {
CodeAction::Tree { depth, hidden, sizes, extensions, no_ignore } => {
// Always use interactive tree
show_interactive_codebase_tree(depth, hidden, sizes, extensions, no_ignore)?;
}
CodeAction::Search { pattern, files, language, context, max_matches, format } => {
let lang = language.and_then(|l| match l.as_str() {
"rust" => Some(SupportedLanguage::Rust),
"javascript" | "js" => Some(SupportedLanguage::JavaScript),
"typescript" | "ts" => Some(SupportedLanguage::TypeScript),
"python" | "py" => Some(SupportedLanguage::Python),
"go" => Some(SupportedLanguage::Go),
"java" => Some(SupportedLanguage::Java),
"c" => Some(SupportedLanguage::C),
"cpp" | "c++" => Some(SupportedLanguage::Cpp),
_ => None,
});
let options = SearchOptions {
pattern,
language: lang,
include_context: context > 0,
context_lines: context,
max_matches: Some(max_matches),
..Default::default()
};
let engine = AstSearchEngine::new(options);
let results = engine.search_files(&files)?;
match format.as_str() {
"json" => {
println!("{}", serde_json::to_string_pretty(&results)?);
}
_ => {
for (file_path, matches) in results {
println!("\n{}:", file_path.display().to_string().bright_blue());
for search_match in matches {
println!(" {}:{} - {}",
search_match.line.to_string().yellow(),
search_match.column.to_string().yellow(),
search_match.matched_text.trim()
);
if !search_match.context_before.is_empty() {
for line in search_match.context_before.lines() {
println!(" {}", line.dimmed());
}
}
if !search_match.context_after.is_empty() {
for line in search_match.context_after.lines() {
println!(" {}", line.dimmed());
}
}
}
}
}
}
}
CodeAction::Transform { pattern, replacement, files, language, dry_run, no_backup, max_changes } => {
let lang = language.and_then(|l| match l.as_str() {
"rust" => Some(SupportedLanguage::Rust),
"javascript" | "js" => Some(SupportedLanguage::JavaScript),
"typescript" | "ts" => Some(SupportedLanguage::TypeScript),
"python" | "py" => Some(SupportedLanguage::Python),
"go" => Some(SupportedLanguage::Go),
"java" => Some(SupportedLanguage::Java),
"c" => Some(SupportedLanguage::C),
"cpp" | "c++" => Some(SupportedLanguage::Cpp),
_ => None,
}).unwrap_or(SupportedLanguage::Rust);
let options = TransformOptions {
dry_run,
backup_files: !no_backup,
max_changes_per_file: Some(max_changes),
..Default::default()
};
let rule = TransformRule {
name: "user_transform".to_string(),
pattern,
replacement,
language: lang,
};
let engine = AstTransformEngine::new(options);
let results = engine.transform_files(&files, &rule)?;
for result in results {
if result.successful {
println!("{}: {} changes applied",
result.file_path.display().to_string().green(),
result.changes_made.to_string().yellow()
);
if dry_run {
println!(" (dry run - no files modified)");
}
} else {
println!("{}: failed - {}",
result.file_path.display().to_string().red(),
result.error_message.unwrap_or_default()
);
}
}
}
CodeAction::Patterns { language, category } => {
let lang = match language.as_str() {
"rust" => SupportedLanguage::Rust,
"javascript" | "js" => SupportedLanguage::JavaScript,
"typescript" | "ts" => SupportedLanguage::TypeScript,
"python" | "py" => SupportedLanguage::Python,
"go" => SupportedLanguage::Go,
"java" => SupportedLanguage::Java,
"c" => SupportedLanguage::C,
"cpp" | "c++" => SupportedLanguage::Cpp,
_ => {
eprintln!("Unsupported language: {}", language);
return Ok(());
}
};
println!("Common {} patterns for {}:", category, language);
if category == "transform" {
let transforms = CommonTransforms::for_language(lang);
for transform in transforms {
println!(" {}: {} -> {}",
transform.name.bright_blue(),
transform.pattern.yellow(),
transform.replacement.green()
);
}
} else {
println!("Search patterns will be available in full implementation");
}
}
CodeAction::Analyze { files, language: _language, analysis_type, format } => {
println!("Code Analysis ({}): analyzing {} files", analysis_type, files.len());
for file in files {
if let Ok(content) = std::fs::read_to_string(&file) {
let lines = content.lines().count();
let chars = content.len();
match format.as_str() {
"json" => {
println!("{{\"file\": \"{}\", \"lines\": {}, \"chars\": {}}}",
file.display(), lines, chars);
}
_ => {
println!("{}: {} lines, {} characters",
file.display().to_string().bright_blue(),
lines.to_string().yellow(),
chars.to_string().yellow()
);
}
}
} else {
println!("{}: could not read file", file.display().to_string().red());
}
}
}
}
Ok(())
}
fn show_codebase_tree(depth: usize, show_hidden: bool, show_sizes: bool, extensions_filter: Option<String>, no_ignore: bool) -> Result<()> {
use colored::Colorize;
use ignore::gitignore::GitignoreBuilder;
let current_dir = std::env::current_dir()?;
let project_root = find_project_root(¤t_dir);
// Show project information
println!("{}", "๐ Codebase Structure".bright_blue().bold());
println!("{} {}", "Project Root:".bright_green(), project_root.display());
println!("{} {}", "Current Location:".bright_yellow(), current_dir.display());
if current_dir != project_root {
let relative_path = current_dir.strip_prefix(&project_root).unwrap_or(¤t_dir);
println!("{} {}", "Relative Path:".bright_cyan(), relative_path.display());
}
println!();
// Parse extensions filter
let extensions: Option<Vec<String>> = extensions_filter.map(|ext_str| {
ext_str.split(',').map(|s| s.trim().to_lowercase()).collect()
});
// Initialize gitignore if needed
let gitignore = if no_ignore {
None
} else {
let mut builder = GitignoreBuilder::new(&project_root);
let _ = builder.add(&project_root.join(".gitignore"));
builder.build().ok()
};
// Display tree
display_tree(&project_root, "", depth, 0, show_hidden, show_sizes, &extensions, &gitignore)?;
Ok(())
}
fn show_interactive_codebase_tree(depth: usize, show_hidden: bool, show_sizes: bool, extensions_filter: Option<String>, no_ignore: bool) -> Result<()> {
use wsb::interactive_tree::InteractiveTree;
let current_dir = std::env::current_dir()?;
let project_root = find_project_root(¤t_dir);
// Show brief project info before launching interactive mode
println!("{}", "๐ณ Interactive Codebase Navigator".bright_blue().bold());
println!("{} {}", "Project Root:".bright_green(), project_root.display());
println!();
println!("{}", "Loading interactive tree... Press 'q' to exit when ready.".dimmed());
// Small delay to let user read the info
std::thread::sleep(std::time::Duration::from_millis(1500));
// Create and run interactive tree
let max_depth = if depth > 0 { Some(depth) } else { None };
let mut tree = InteractiveTree::new(&project_root, max_depth, show_hidden)?;
// Set callback for when Enter is pressed
tree.set_callback(|selected_paths| {
if !selected_paths.is_empty() {
println!("\n๐ฏ Selected items:");
for path in selected_paths {
println!(" โข {}", path.display().to_string().bright_cyan());
}
println!("\n{}", "โ Callback executed! Press any key to continue...".bright_green());
// Wait for user input before continuing
use std::io::Read;
let mut buffer = [0; 1];
let _ = std::io::stdin().read(&mut buffer);
} else {
println!("\n{}", "No items selected.".yellow());
}
Ok(())
});
tree.run()?;
println!("\n{}", "Interactive navigation completed.".bright_green());
Ok(())
}
fn find_project_root(current: &Path) -> std::path::PathBuf {
let mut path = current.to_path_buf();
// Look for common project markers
let project_markers = [
"Cargo.toml", "package.json", "pyproject.toml", "setup.py",
"composer.json", "pom.xml", "build.gradle", "CMakeLists.txt",
".git", ".svn", ".hg", "Makefile", "go.mod"
];
loop {
for marker in &project_markers {
if path.join(marker).exists() {
return path;
}
}
if !path.pop() {
break;
}
}
// If no markers found, return current directory
current.to_path_buf()
}
fn display_tree(
dir: &Path,
prefix: &str,
max_depth: usize,
current_depth: usize,
show_hidden: bool,
show_sizes: bool,
extensions: &Option<Vec<String>>,
gitignore: &Option<ignore::gitignore::Gitignore>
) -> Result<()> {
if current_depth >= max_depth {
return Ok(());
}
let entries = fs::read_dir(dir)?;
let mut entries: Vec<_> = entries.collect::<Result<Vec<_>, _>>()?;
// Filter out gitignored files first
if let Some(ref gi) = gitignore {
entries.retain(|entry| {
let path = entry.path();
let is_dir = entry.file_type().map(|ft| ft.is_dir()).unwrap_or(false);
let matched = gi.matched(&path, is_dir);
!matched.is_ignore()
});
}
entries.sort_by_key(|entry| {
let is_dir = entry.file_type().map(|ft| ft.is_dir()).unwrap_or(false);
(!is_dir, entry.file_name())
});
let total_entries = entries.len();
for (index, entry) in entries.iter().enumerate() {
let file_name = entry.file_name();
let file_name_str = file_name.to_string_lossy();
let path = entry.path();
let is_last = index == total_entries - 1;
// Skip hidden files unless requested
if !show_hidden && file_name_str.starts_with('.') {
continue;
}
let is_dir = entry.file_type()?.is_dir();
let connector = if is_last { "โโโ " } else { "โโโ " };
let new_prefix = if is_last { " " } else { "โ " };
// Apply extension filter for files
if !is_dir {
if let Some(ref exts) = extensions {
if let Some(ext) = path.extension() {
let ext_str = ext.to_string_lossy().to_lowercase();
if !exts.contains(&ext_str) {
continue;
}
} else if !exts.is_empty() {
continue;
}
}
}
// Format output
print!("{}{}", prefix, connector);
if is_dir {
print!("{}", file_name_str.bright_blue().bold());
} else {
let colored_name = match path.extension().and_then(|s| s.to_str()) {
Some("rs") => file_name_str.bright_red(),
Some("py") => file_name_str.bright_yellow(),
Some("js" | "ts" | "jsx" | "tsx") => file_name_str.bright_green(),
Some("json" | "yaml" | "yml" | "toml") => file_name_str.bright_cyan(),
Some("md" | "txt" | "doc") => file_name_str.white(),
Some("sh" | "bash" | "zsh") => file_name_str.bright_magenta(),
_ => file_name_str.normal(),
};
print!("{}", colored_name);
}
// Show file size if requested
if show_sizes && !is_dir {
if let Ok(metadata) = entry.metadata() {
let size = format_file_size(metadata.len());
print!(" {}", size.dimmed());
}
}
println!();
// Recurse into directories
if is_dir && current_depth + 1 < max_depth {
let next_prefix = format!("{}{}", prefix, new_prefix);
display_tree(&path, &next_prefix, max_depth, current_depth + 1, show_hidden, show_sizes, extensions, gitignore)?;
}
}
Ok(())
}
// Helper structures and functions
#[derive(Debug)]
struct ArtifactInfo {
path: String,
category: String,
size: u64,
modified_time: std::time::SystemTime,
session_id: Option<String>,
description: Option<String>,
}
#[derive(Debug)]
struct SearchResult {
path: String,
match_info: String,
}
fn collect_artifacts_recursive(
dir: &Path,
category: &str,
artifacts: &mut Vec<ArtifactInfo>
) -> anyhow::Result<()> {
use std::fs;
if !dir.exists() || !dir.is_dir() {
return Ok(());
}
for entry in fs::read_dir(dir)? {
let entry = entry?;
let path = entry.path();
let metadata = entry.metadata()?;
if path.is_file() {
let session_id = extract_session_id_from_path(&path);
artifacts.push(ArtifactInfo {
path: path.display().to_string(),
category: category.to_string(),
size: metadata.len(),
modified_time: metadata.modified()?,
session_id,
description: None,
});
} else if path.is_dir() {
collect_artifacts_recursive(&path, category, artifacts)?;
}
}
Ok(())
}
fn format_file_size(bytes: u64) -> String {
const UNITS: &[&str] = &["B", "KB", "MB", "GB"];
let mut size = bytes as f64;
let mut unit_index = 0;
while size >= 1024.0 && unit_index < UNITS.len() - 1 {
size /= 1024.0;
unit_index += 1;
}
if unit_index == 0 {
format!("{} {}", bytes, UNITS[unit_index])
} else {
format!("{:.1} {}", size, UNITS[unit_index])
}
}
fn extract_session_id_from_path(_path: &Path) -> Option<String> {
None
}
fn find_old_files(dir: &Path, cutoff: std::time::SystemTime, files: &mut Vec<PathBuf>) -> anyhow::Result<()> {
use std::fs;
if !dir.exists() || !dir.is_dir() {
return Ok(());
}
for entry in fs::read_dir(dir)? {
let entry = entry?;
let path = entry.path();
if path.is_file() {
let metadata = entry.metadata()?;
if metadata.modified()? < cutoff {
files.push(path);
}
} else if path.is_dir() {
find_old_files(&path, cutoff, files)?;
}
}
Ok(())
}
fn search_artifacts_recursive(
dir: &Path,
query: &str,
content: bool,
names: bool,
results: &mut Vec<SearchResult>
) -> anyhow::Result<()> {
use std::fs;
if !dir.exists() || !dir.is_dir() {
return Ok(());
}
for entry in fs::read_dir(dir)? {
let entry = entry?;
let path = entry.path();
if path.is_file() {
let path_str = path.display().to_string();
let mut matches = Vec::new();
if names || !content {
if path_str.contains(query) {
matches.push("filename match".to_string());
}
}
if content {
if let Ok(file_content) = fs::read_to_string(&path) {
if file_content.contains(query) {
matches.push("content match".to_string());
}
}
}
if !matches.is_empty() {
results.push(SearchResult {
path: path_str,
match_info: matches.join(", "),
});
}
} else if path.is_dir() {
search_artifacts_recursive(&path, query, content, names, results)?;
}
}
Ok(())
}
fn run_version_command(action: VersionAction) -> Result<()> {
match action {
VersionAction::Show { verbose, format } => {
handle_version_show(verbose, format)
}
VersionAction::Major { version } => {
handle_version_major(version)
}
VersionAction::Tag { prefix, message } => {
handle_version_tag(prefix, message)
}
VersionAction::Info { include_history } => {
handle_version_info(include_history)
}
}
}
fn handle_version_show(verbose: bool, format: String) -> Result<()> {
let rt = tokio::runtime::Runtime::new()?;
rt.block_on(async {
let db_path = get_project_root()?.join(".wsb/project.db");
let pool = wsb::entities::database::initialize_database(&db_path).await?;
let entity_manager = EntityManager::new(pool.clone());
// Get current project and major version
let project = entity_manager.get_current_project().await?;
let major_version = get_project_major_version(&pool).await?;
// Calculate version using new system
let version_info = wsb::st8::VersionInfo::calculate_with_major(major_version)?;
match format.as_str() {
"json" => {
let json_output = if verbose {
let calc_info = wsb::st8::VersionInfo::get_calculation_info(major_version)?;
serde_json::json!({
"version": version_info.full_version,
"major": major_version,
"minor": version_info.minor_version,
"patch": version_info.patch_version,
"project": project.map(|p| p.name.clone()).unwrap_or_else(|| "Unknown".to_string()),
"calculation": {
"total_commits": calc_info.total_commits,
"changes_since_release": calc_info.changes_since_release,
"last_release_tag": calc_info.last_release_tag,
"method": calc_info.calculation_method
}
})
} else {
serde_json::json!({
"version": version_info.full_version,
"major": major_version,
"minor": version_info.minor_version,
"patch": version_info.patch_version,
"project": project.map(|p| p.name.clone()).unwrap_or_else(|| "Unknown".to_string())
})
};
println!("{}", serde_json::to_string_pretty(&json_output)?);
}
_ => {
println!("{} {}", "Version:".blue().bold(), version_info.full_version.green().bold());
let project_name = project.map(|p| p.name.clone()).unwrap_or_else(|| "Unknown".to_string());
println!("{} {}", "Project:".blue(), project_name);
if verbose {
let calc_info = wsb::st8::VersionInfo::get_calculation_info(major_version)?;
println!("\n{}", "Calculation Breakdown:".blue().bold());
println!(" {} {}", "Major (DB):".blue(), major_version.to_string().yellow());
println!(" {} {}", "Minor (commits):".blue(), version_info.minor_version.to_string().yellow());
println!(" {} {}", "Patch (changes):".blue(), version_info.patch_version.to_string().yellow());
if let Some(tag) = calc_info.last_release_tag {
println!(" {} {}", "Last release tag:".blue(), tag.green());
} else {
println!(" {} {}", "Last release tag:".blue(), "none".yellow());
}
println!(" {} {}", "Method:".blue(), calc_info.calculation_method.cyan());
}
}
}
anyhow::Ok(())
})
}
fn handle_version_major(version: u32) -> Result<()> {
let rt = tokio::runtime::Runtime::new()?;
rt.block_on(async {
let db_path = get_project_root()?.join(".wsb/project.db");
let pool = wsb::entities::database::initialize_database(&db_path).await?;
// Update major version in database
sqlx::query("UPDATE projects SET major_version = ?, updated_at = datetime('now') WHERE id = (SELECT id FROM projects LIMIT 1)")
.bind(version)
.execute(&pool)
.await?;
// Calculate new version
let version_info = wsb::st8::VersionInfo::calculate_with_major(version)?;
log::info!("Major version set to {}, new version: {}", version, version_info.full_version);
println!("{} Major version set to {}", "โ
".green(), version.to_string().green().bold());
println!("{} New version: {}", "๐ข".blue(), version_info.full_version.green().bold());
log::info!("Version set successfully - minor/patch calculated from git state");
println!("\n{} {}", "Note:".blue(), "Minor/patch are calculated from git state");
println!("{} Use 'wsb version tag' to create a release tag", "๐ก".yellow());
anyhow::Ok(())
})
}
fn handle_version_tag(prefix: String, message: Option<String>) -> Result<()> {
let rt = tokio::runtime::Runtime::new()?;
rt.block_on(async {
let db_path = get_project_root()?.join(".wsb/project.db");
let pool = wsb::entities::database::initialize_database(&db_path).await?;
let major_version = get_project_major_version(&pool).await?;
let version_info = wsb::st8::VersionInfo::calculate_with_major(major_version)?;
let tag_name = format!("{}{}", prefix, version_info.full_version);
let tag_message = message.unwrap_or_else(|| format!("Release version {}", version_info.full_version));
// Create git tag
let output = Command::new("git")
.args(["tag", "-a", &tag_name, "-m", &tag_message])
.output()
.context("Failed to create git tag")?;
if !output.status.success() {
let stderr = String::from_utf8_lossy(&output.stderr);
anyhow::bail!("Failed to create git tag: {}", stderr);
}
log::info!("Created git tag: {}", tag_name);
println!("{} Created git tag: {}", "โ
".green(), tag_name.green().bold());
println!("{} Message: {}", "๐".blue(), tag_message);
println!("\n{} To push tag to remote: git push origin {}", "๐ก".yellow(), tag_name);
anyhow::Ok(())
})
}
fn handle_version_info(include_history: bool) -> Result<()> {
let rt = tokio::runtime::Runtime::new()?;
rt.block_on(async {
let db_path = get_project_root()?.join(".wsb/project.db");
let pool = wsb::entities::database::initialize_database(&db_path).await?;
let major_version = get_project_major_version(&pool).await?;
let calc_info = wsb::st8::VersionInfo::get_calculation_info(major_version)?;
let version_info = wsb::st8::VersionInfo::calculate_with_major(major_version)?;
println!("{}", "Version Calculation Information".blue().bold());
println!("=====================================");
println!();
println!("{}", "Current Version Breakdown:".green().bold());
println!(" {} {}.{}.{}", "Version:".blue(), major_version.to_string().green(),
version_info.minor_version.to_string().yellow(),
version_info.patch_version.to_string().cyan());
println!(" {} {} (stored in database)", "Major:".blue(), major_version.to_string().green());
println!(" {} {} (total git commits)", "Minor:".blue(), version_info.minor_version.to_string().yellow());
println!(" {} {} (changes since last v{}.* tag)", "Patch:".blue(), version_info.patch_version.to_string().cyan(), major_version);
println!();
println!("{}", "Git Integration:".green().bold());
if let Some(ref git_root) = calc_info.git_root {
println!(" {} {}", "Git Root:".blue(), git_root.display().to_string().cyan());
}
if let Some(ref tag) = calc_info.last_release_tag {
println!(" {} {}", "Last Release Tag:".blue(), tag.green());
} else {
println!(" {} {}", "Last Release Tag:".blue(), "none found".yellow());
}
println!();
println!("{}", "Commands:".green().bold());
println!(" {} Set major version", format!("wsb version major <number>").cyan());
println!(" {} Create release tag", "wsb version tag".cyan());
println!(" {} Show current version", "wsb version show".cyan());
println!();
if include_history {
println!("{}", "Git History Analysis:".green().bold());
// Show recent tags
let output = Command::new("git")
.args(["tag", "--list", "--sort=-version:refname"])
.output();
if let Ok(output) = output {
if output.status.success() {
let tags = String::from_utf8_lossy(&output.stdout);
if !tags.trim().is_empty() {
println!(" {}:", "Recent Tags".blue());
for (i, tag) in tags.lines().take(5).enumerate() {
println!(" {}. {}", i + 1, tag.trim().green());
}
} else {
println!(" {} {}", "Recent Tags:".blue(), "none found".yellow());
}
} else {
println!(" {} {}", "Recent Tags:".blue(), "error retrieving".red());
}
}
println!();
}
anyhow::Ok(())
})
}
async fn get_project_major_version(pool: &SqlitePool) -> Result<u32> {
let row = sqlx::query("SELECT major_version FROM projects LIMIT 1")
.fetch_optional(pool)
.await?;
if let Some(row) = row {
Ok(row.get::<i64, _>("major_version") as u32)
} else {
// No project found, return default
Ok(0)
}
}
fn handle_test_command(dry_run: bool, install: bool, args: Vec<String>) -> Result<()> {
use wsb::st8::detect_project_files;
// Get project root (current directory)
let project_root = get_project_root()?;
// Detect project files to determine project type
let project_files = detect_project_files(&project_root)?;
if project_files.is_empty() {
println!("{} No project files detected in current directory", "โ ๏ธ".yellow());
println!("{} Supported project types: Rust (Cargo.toml), Node.js (package.json), Python (pyproject.toml), PHP (composer.json), Dart (pubspec.yaml), Java (pom.xml, build.gradle), C++ (CMakeLists.txt)", "๐ก".blue());
return Ok(());
}
// Determine primary project type and test command
let (test_cmd, test_args, description) = determine_test_command(&project_files)?;
println!("{} Detected project: {}", "๐".blue(), description);
println!("{} Test command: {} {}", "๐".green(), test_cmd, test_args.join(" "));
if !args.is_empty() {
println!("{} Additional args: {}", "๐".blue(), args.join(" "));
}
if dry_run {
println!("{} Dry run - would execute:", "๐".cyan());
let mut full_args = test_args.clone();
full_args.extend(args);
println!(" {} {}", test_cmd, full_args.join(" "));
return Ok(());
}
// Check if test runner is available and install if needed
if install {
ensure_test_runner_available(&test_cmd, &project_files)?;
}
// Execute the test command
let mut full_args = test_args.clone();
full_args.extend(args);
println!("{} Running tests...", "๐งช".green());
let mut command = Command::new(&test_cmd);
command.args(&full_args);
let status = command.status()?;
if status.success() {
println!("{} Tests completed successfully!", "โ
".green());
} else {
println!("{} Tests failed with exit code: {}", "โ".red(), status.code().unwrap_or(-1));
std::process::exit(status.code().unwrap_or(1));
}
Ok(())
}
fn determine_test_command(project_files: &[wsb::st8::ProjectFile]) -> Result<(String, Vec<String>, String)> {
use wsb::st8::ProjectFileType;
// Priority order for when multiple project files exist
for project_file in project_files {
match project_file.file_type {
ProjectFileType::CargoToml => {
return Ok((
"cargo".to_string(),
vec!["nextest".to_string(), "run".to_string(), "--no-fail-fast".to_string()],
"Rust (Cargo.toml)".to_string(),
));
}
ProjectFileType::PackageJson => {
return Ok((
"npm".to_string(),
vec!["test".to_string()],
"Node.js (package.json)".to_string(),
));
}
ProjectFileType::PyprojectToml => {
return Ok((
"pytest".to_string(),
vec![],
"Python (pyproject.toml)".to_string(),
));
}
ProjectFileType::SetupPy => {
return Ok((
"python".to_string(),
vec!["-m".to_string(), "pytest".to_string()],
"Python (setup.py)".to_string(),
));
}
ProjectFileType::ComposerJson => {
return Ok((
"vendor/bin/phpunit".to_string(),
vec![],
"PHP (composer.json)".to_string(),
));
}
ProjectFileType::PubspecYaml => {
return Ok((
"flutter".to_string(),
vec!["test".to_string()],
"Dart/Flutter (pubspec.yaml)".to_string(),
));
}
ProjectFileType::PomXml => {
return Ok((
"mvn".to_string(),
vec!["test".to_string()],
"Java Maven (pom.xml)".to_string(),
));
}
ProjectFileType::BuildGradle => {
return Ok((
"./gradlew".to_string(),
vec!["test".to_string()],
"Java Gradle (build.gradle)".to_string(),
));
}
ProjectFileType::BuildGradleKts => {
return Ok((
"./gradlew".to_string(),
vec!["test".to_string()],
"Kotlin Gradle (build.gradle.kts)".to_string(),
));
}
ProjectFileType::CMakeLists => {
return Ok((
"ctest".to_string(),
vec!["--output-on-failure".to_string()],
"C/C++ (CMakeLists.txt)".to_string(),
));
}
ProjectFileType::PackageSwift => {
return Ok((
"swift".to_string(),
vec!["test".to_string()],
"Swift (Package.swift)".to_string(),
));
}
ProjectFileType::Gemspec => {
return Ok((
"bundle".to_string(),
vec!["exec".to_string(), "rspec".to_string()],
"Ruby (gemspec)".to_string(),
));
}
ProjectFileType::Csproj => {
return Ok((
"dotnet".to_string(),
vec!["test".to_string()],
".NET (csproj)".to_string(),
));
}
ProjectFileType::GoMod => {
return Ok((
"go".to_string(),
vec!["test".to_string(), "./...".to_string()],
"Go (go.mod)".to_string(),
));
}
ProjectFileType::MixExs => {
return Ok((
"mix".to_string(),
vec!["test".to_string()],
"Elixir (mix.exs)".to_string(),
));
}
ProjectFileType::BuildSbt => {
return Ok((
"sbt".to_string(),
vec!["test".to_string()],
"Scala (build.sbt)".to_string(),
));
}
ProjectFileType::ShardYml => {
return Ok((
"crystal".to_string(),
vec!["spec".to_string()],
"Crystal (shard.yml)".to_string(),
));
}
ProjectFileType::JuliaProject => {
return Ok((
"julia".to_string(),
vec!["--project=.".to_string(), "-e".to_string(), "using Pkg; Pkg.test()".to_string()],
"Julia (Project.toml)".to_string(),
));
}
}
}
anyhow::bail!("No supported test runner found for detected project files")
}
fn ensure_test_runner_available(test_cmd: &str, project_files: &[wsb::st8::ProjectFile]) -> Result<()> {
use wsb::st8::ProjectFileType;
// Check if command exists
let check_status = Command::new("which")
.arg(test_cmd)
.output();
if let Ok(output) = check_status {
if output.status.success() {
println!("{} Test runner {} is already available", "โ
".green(), test_cmd);
return Ok(());
}
}
// Try to install missing test runners
for project_file in project_files {
match project_file.file_type {
ProjectFileType::CargoToml if test_cmd.contains("nextest") => {
println!("{} Installing cargo-nextest...", "๐ฆ".blue());
let status = Command::new("cargo")
.args(["install", "cargo-nextest", "--locked"])
.status()?;
if status.success() {
println!("{} Successfully installed cargo-nextest", "โ
".green());
} else {
println!("{} Failed to install cargo-nextest, falling back to standard cargo test", "โ ๏ธ".yellow());
// Could modify the test command here to fallback to regular cargo test
}
return Ok(());
}
ProjectFileType::PyprojectToml | ProjectFileType::SetupPy if test_cmd.contains("pytest") => {
println!("{} Installing pytest...", "๐ฆ".blue());
let status = Command::new("pip")
.args(["install", "pytest"])
.status()?;
if status.success() {
println!("{} Successfully installed pytest", "โ
".green());
} else {
println!("{} Failed to install pytest", "โ".red());
anyhow::bail!("Could not install pytest");
}
return Ok(());
}
_ => {}
}
}
println!("{} Test runner {} not found, please install it manually", "โ ๏ธ".yellow(), test_cmd);
Ok(())
}