From 6e01f9995816f2ae52bcfacf7d896de797a1a6ef Mon Sep 17 00:00:00 2001 From: despiegk Date: Tue, 5 Aug 2025 15:43:13 +0200 Subject: [PATCH] ... --- cargo_instructions.md | 0 packages/Cargo.toml | 83 +-- packages/core/logger/instructions.md | 825 +++++++++++++++++++++++++++ 3 files changed, 852 insertions(+), 56 deletions(-) create mode 100644 cargo_instructions.md create mode 100644 packages/core/logger/instructions.md diff --git a/cargo_instructions.md b/cargo_instructions.md new file mode 100644 index 0000000..e69de29 diff --git a/packages/Cargo.toml b/packages/Cargo.toml index 89dad2f..d284bb4 100644 --- a/packages/Cargo.toml +++ b/packages/Cargo.toml @@ -12,23 +12,18 @@ readme = "README.md" [workspace] members = [ - ".", - "vault", - "git", - "redisclient", - "mycelium", - "text", - "os", - "net", - "zinit_client", - "process", - "virt", - "zos", - "postgresclient", - "kubernetes", - "rhai", - "herodo", - "service_manager", + "clients/myceliumclient", + "clients/postgresclient", + "clients/redisclient", + "clients/zinitclient", + "core/net", + "core/text", + "crypt/vault", + "system/git", + "system/kubernetes", + "system/os", + "system/process", + "system/virt", ] resolver = "2" @@ -89,25 +84,22 @@ urlencoding = "2.1.3" tokio-test = "0.4.4" [dependencies] -thiserror = "2.0.12" # For error handling in the main Error enum -tokio = { workspace = true } # For async examples +thiserror = { workspace = true } +tokio = { workspace = true } # Optional dependencies - users can choose which modules to include -sal-git = { path = "git", optional = true } -sal-kubernetes = { path = "kubernetes", optional = true } -sal-redisclient = { path = "redisclient", optional = true } -sal-mycelium = { path = "mycelium", optional = true } -sal-text = { path = "text", optional = true } -sal-os = { path = "os", optional = true } -sal-net = { path = "net", optional = true } -sal-zinit-client = { path = "zinit_client", optional = true } -sal-process = { path = "process", optional = true } -sal-virt = { path = "virt", optional = true } -sal-postgresclient = { path = "postgresclient", optional = true } -sal-vault = { path = "vault", optional = true } -sal-rhai = { path = "rhai", optional = true } -sal-service-manager = { path = "service_manager", optional = true } -zinit-client.workspace = true +sal-git = { path = "../system/git", optional = true } +sal-kubernetes = { path = "../system/kubernetes", optional = true } +sal-redisclient = { path = "../clients/redisclient", optional = true } +sal-mycelium = { path = "../clients/myceliumclient", optional = true } +sal-text = { path = "../core/text", optional = true } +sal-os = { path = "../system/os", optional = true } +sal-net = { path = "../core/net", optional = true } +sal-zinit-client = { path = "../clients/zinitclient", optional = true } +sal-process = { path = "../system/process", optional = true } +sal-virt = { path = "../system/virt", optional = true } +sal-postgresclient = { path = "../clients/postgresclient", optional = true } +sal-vault = { path = "../crypt/vault", optional = true } [features] default = [] @@ -125,14 +117,11 @@ process = ["dep:sal-process"] virt = ["dep:sal-virt"] postgresclient = ["dep:sal-postgresclient"] vault = ["dep:sal-vault"] -rhai = ["dep:sal-rhai"] -service_manager = ["dep:sal-service-manager"] # Convenience feature groups core = ["os", "process", "text", "net"] clients = ["redisclient", "postgresclient", "zinit_client", "mycelium"] -infrastructure = ["git", "vault", "kubernetes", "virt", "service_manager"] -scripting = ["rhai"] +infrastructure = ["git", "vault", "kubernetes", "virt"] all = [ "git", "kubernetes", @@ -146,22 +135,4 @@ all = [ "virt", "postgresclient", "vault", - "rhai", - "service_manager", ] - -# Examples -[[example]] -name = "postgres_cluster" -path = "examples/kubernetes/clusters/postgres.rs" -required-features = ["kubernetes"] - -[[example]] -name = "redis_cluster" -path = "examples/kubernetes/clusters/redis.rs" -required-features = ["kubernetes"] - -[[example]] -name = "generic_cluster" -path = "examples/kubernetes/clusters/generic.rs" -required-features = ["kubernetes"] diff --git a/packages/core/logger/instructions.md b/packages/core/logger/instructions.md new file mode 100644 index 0000000..ddecebe --- /dev/null +++ b/packages/core/logger/instructions.md @@ -0,0 +1,825 @@ + +/Users/despiegk/code/github/freeflowuniverse/herolib +├── aiprompts +│ └── herolib_core +│ ├── core_ourtime.md +│ ├── core_paths.md +│ └── core_text.md +└── lib + └── core + └── logger + ├── factory.v + ├── log_test.v + ├── log.v + ├── model.v + ├── readme.md + └── search.v + + + + +File: /Users/despiegk/code/github/freeflowuniverse/herolib/lib/core/logger/factory.v +```v +module logger + +import freeflowuniverse.herolib.core.pathlib + +pub fn new(path string) !Logger { + mut p := pathlib.get_dir(path: path, create: true)! + return Logger{ + path: p + lastlog_time: 0 + } +} + +``` + +File: /Users/despiegk/code/github/freeflowuniverse/herolib/lib/core/logger/log_test.v +```v +module logger + +import os +import freeflowuniverse.herolib.data.ourtime +import freeflowuniverse.herolib.core.pathlib + +fn testsuite_begin() { + if os.exists('/tmp/testlogs') { + os.rmdir_all('/tmp/testlogs')! + } +} + +fn test_logger() { + mut logger := new('/tmp/testlogs')! + + // Test stdout logging + logger.log(LogItemArgs{ + cat: 'test-app' + log: 'This is a test message\nWith a second line\nAnd a third line' + logtype: .stdout + timestamp: ourtime.new('2022-12-05 20:14:35')! + })! + + // Test error logging + logger.log(LogItemArgs{ + cat: 'error-test' + log: 'This is an error\nWith details' + logtype: .error + timestamp: ourtime.new('2022-12-05 20:14:35')! + })! + + logger.log(LogItemArgs{ + cat: 'test-app' + log: 'This is a test message\nWith a second line\nAnd a third line' + logtype: .stdout + timestamp: ourtime.new('2022-12-05 20:14:36')! + })! + + logger.log(LogItemArgs{ + cat: 'error-test' + log: ' + This is an error + + With details + ' + logtype: .error + timestamp: ourtime.new('2022-12-05 20:14:36')! + })! + + logger.log(LogItemArgs{ + cat: 'error-test' + log: ' + aaa + + bbb + ' + logtype: .error + timestamp: ourtime.new('2022-12-05 22:14:36')! + })! + + logger.log(LogItemArgs{ + cat: 'error-test' + log: ' + aaa2 + + bbb2 + ' + logtype: .error + timestamp: ourtime.new('2022-12-05 22:14:36')! + })! + + // Verify log directory exists + assert os.exists('/tmp/testlogs'), 'Log directory should exist' + + // Get log file + files := os.ls('/tmp/testlogs')! + assert files.len == 2 + + mut file := pathlib.get_file( + path: '/tmp/testlogs/${files[0]}' + create: false + )! + + content := file.read()!.trim_space() + + items_stdout := logger.search( + timestamp_from: ourtime.new('2022-11-1 20:14:35')! + timestamp_to: ourtime.new('2025-11-1 20:14:35')! + logtype: .stdout + )! + assert items_stdout.len == 2 + + items_error := logger.search( + timestamp_from: ourtime.new('2022-11-1 20:14:35')! + timestamp_to: ourtime.new('2025-11-1 20:14:35')! + logtype: .error + )! + assert items_error.len == 4 +} + +fn testsuite_end() { + // if os.exists('/tmp/testlogs') { + // os.rmdir_all('/tmp/testlogs')! + // } +} + +``` + +File: /Users/despiegk/code/github/freeflowuniverse/herolib/lib/core/logger/log.v +```v +module logger + +import os +import freeflowuniverse.herolib.core.texttools +import freeflowuniverse.herolib.data.ourtime + +@[params] +pub struct LogItemArgs { +pub mut: + timestamp ?ourtime.OurTime + cat string + log string + logtype LogType +} + +pub fn (mut l Logger) log(args_ LogItemArgs) ! { + mut args := args_ + + t := args.timestamp or { + t2 := ourtime.now() + t2 + } + + // Format category (max 10 chars, ascii only) + args.cat = texttools.name_fix(args.cat) + if args.cat.len > 10 { + return error('category cannot be longer than 10 chars') + } + args.cat = texttools.expand(args.cat, 10, ' ') + + args.log = texttools.dedent(args.log).trim_space() + + mut logfile_path := '${l.path.path}/${t.dayhour()}.log' + + // Create log file if it doesn't exist + if !os.exists(logfile_path) { + os.write_file(logfile_path, '')! + l.lastlog_time = 0 // make sure we put time again + } + + mut f := os.open_append(logfile_path)! + + mut content := '' + + // Add timestamp if we're in a new second + if t.unix() > l.lastlog_time { + content += '\n${t.time().format_ss()}\n' + l.lastlog_time = t.unix() + } + + // Format log lines + error_prefix := if args.logtype == .error { 'E' } else { ' ' } + lines := args.log.split('\n') + + for i, line in lines { + if i == 0 { + content += '${error_prefix} ${args.cat} - ${line}\n' + } else { + content += '${error_prefix} ${line}\n' + } + } + f.writeln(content.trim_space_right())! + f.close() +} + +``` + +File: /Users/despiegk/code/github/freeflowuniverse/herolib/lib/core/logger/model.v +```v +module logger + +import freeflowuniverse.herolib.data.ourtime +import freeflowuniverse.herolib.core.pathlib + +@[heap] +pub struct Logger { +pub mut: + path pathlib.Path + lastlog_time i64 // to see in log format, every second we put a time down, we need to know if we are in a new second (logs can come in much faster) +} + +pub struct LogItem { +pub mut: + timestamp ourtime.OurTime + cat string + log string + logtype LogType +} + +pub enum LogType { + stdout + error +} + +``` + +File: /Users/despiegk/code/github/freeflowuniverse/herolib/lib/core/logger/readme.md +```md +# Logger Module + +A simple logging system that provides structured logging with search capabilities. + +Logs are stored in hourly files with a consistent format that makes them both human-readable and machine-parseable. + +## Features + +- Structured logging with categories and error types +- Automatic timestamp management +- Multi-line message support +- Search functionality with filtering options +- Human-readable log format + +## Usage + +```v +import freeflowuniverse.herolib.core.logger +import freeflowuniverse.herolib.data.ourtime + +// Create a new logger +mut l := logger.new(path: '/var/logs')! + +// Log a message +l.log( + cat: 'system', + log: 'System started successfully', + logtype: .stdout +)! + +// Log an error +l.log( + cat: 'system', + log: 'Failed to connect\nRetrying in 5 seconds...', + logtype: .error +)! + +// Search logs +results := l.search( + timestamp_from: ourtime.now().warp("-24h"), // Last 24 hours + cat: 'system', // Filter by category + log: 'failed', // Search in message content + logtype: .error, // Only error messages + maxitems: 100 // Limit results +)! +``` + +## Log Format + +Each log file is named using the format `YYYY-MM-DD-HH.log` and contains entries in the following format: + +``` +21:23:42 + system - This is a normal log message + system - This is a multi-line message + second line with proper indentation + third line maintaining alignment +E error_cat - This is an error message +E second line of error +E third line of error +``` + +### Format Rules + +- Time stamps (HH:MM:SS) are written once per second when the log time changes +- Categories are: + - Limited to 10 characters maximum + - Padded with spaces to exactly 10 characters + - Any `-` in category names are converted to `_` +- Each line starts with either: + - ` ` (space) for normal logs (LogType.stdout) + - `E` for error logs (LogType.error) +- Multi-line messages maintain consistent indentation (14 spaces after the prefix) + +``` + +File: /Users/despiegk/code/github/freeflowuniverse/herolib/lib/core/logger/search.v +```v +module logger + +import os +import freeflowuniverse.herolib.core.texttools +import freeflowuniverse.herolib.data.ourtime + +@[params] +pub struct SearchArgs { +pub mut: + timestamp_from ?ourtime.OurTime + timestamp_to ?ourtime.OurTime + cat string // can be empty + log string // any content in here will be looked for + logtype LogType + maxitems int = 10000 +} + +pub fn (mut l Logger) search(args_ SearchArgs) ![]LogItem { + mut args := args_ + + // Format category (max 10 chars, ascii only) + args.cat = texttools.name_fix(args.cat) + if args.cat.len > 10 { + return error('category cannot be longer than 10 chars') + } + + mut timestamp_from := args.timestamp_from or { ourtime.OurTime{} } + mut timestamp_to := args.timestamp_to or { ourtime.OurTime{} } + + // Get time range + from_time := timestamp_from.unix() + to_time := timestamp_to.unix() + if from_time > to_time { + return error('from_time cannot be after to_time: ${from_time} < ${to_time}') + } + + mut result := []LogItem{} + + // Find log files in time range + mut files := os.ls(l.path.path)! + files.sort() + + for file in files { + if !file.ends_with('.log') { + continue + } + + // Parse dayhour from filename + dayhour := file[..file.len - 4] // remove .log + file_time := ourtime.new(dayhour)! + mut current_time := ourtime.OurTime{} + mut current_item := LogItem{} + mut collecting := false + + // Skip if file is outside time range + if file_time.unix() < from_time || file_time.unix() > to_time { + continue + } + + // Read and parse log file + content := os.read_file('${l.path.path}/${file}')! + lines := content.split('\n') + + for line in lines { + if result.len >= args.maxitems { + return result + } + + line_trim := line.trim_space() + if line_trim == '' { + continue + } + + // Check if this is a timestamp line + if !(line.starts_with(' ') || line.starts_with('E')) { + current_time = ourtime.new(line_trim)! + if collecting { + process(mut result, current_item, current_time, args, from_time, to_time)! + } + collecting = false + continue + } + + if collecting && line.len > 14 && line[13] == `-` { + process(mut result, current_item, current_time, args, from_time, to_time)! + collecting = false + } + + // Parse log line + is_error := line.starts_with('E') + if !collecting { + // Start new item + current_item = LogItem{ + timestamp: current_time + cat: line[2..12].trim_space() + log: line[15..].trim_space() + logtype: if is_error { .error } else { .stdout } + } + // println('new current item: ${current_item}') + collecting = true + } else { + // Continuation line + if line_trim.len < 16 { + current_item.log += '\n' + } else { + current_item.log += '\n' + line[15..] + } + } + } + + // Add last item if collecting + if collecting { + process(mut result, current_item, current_time, args, from_time, to_time)! + } + } + + return result +} + +fn process(mut result []LogItem, current_item LogItem, current_time ourtime.OurTime, args SearchArgs, from_time i64, to_time i64) ! { + // Add previous item if it matches filters + log_epoch := current_item.timestamp.unix() + if log_epoch < from_time || log_epoch > to_time { + return + } + if (args.cat == '' || current_item.cat.trim_space() == args.cat) + && (args.log == '' || current_item.log.contains(args.log)) + && args.logtype == current_item.logtype { + result << current_item + } +} + +``` + +File: /Users/despiegk/code/github/freeflowuniverse/herolib/aiprompts/herolib_core/core_ourtime.md +```md +# OurTime Module + +The `OurTime` module in V provides flexible time handling, supporting relative and absolute time formats, Unix timestamps, and formatting utilities. + +## Key Features +- Create time objects from strings or current time +- Relative time expressions (e.g., `+1h`, `-2d`) +- Absolute time formats (e.g., `YYYY-MM-DD HH:mm:ss`) +- Unix timestamp conversion +- Time formatting and warping + +## Basic Usage + +```v +import freeflowuniverse.herolib.data.ourtime + +// Current time +mut t := ourtime.now() + +// From string +t2 := ourtime.new('2022-12-05 20:14:35')! + +// Get formatted string +println(t2.str()) // e.g., 2022-12-05 20:14 + +// Get Unix timestamp +println(t2.unix()) // e.g., 1670271275 +``` + +## Time Formats + +### Relative Time + +Use `s` (seconds), `h` (hours), `d` (days), `w` (weeks), `M` (months), `Q` (quarters), `Y` (years). + +```v +// Create with relative time +mut t := ourtime.new('+1w +2d -4h')! + +// Warp existing time +mut t2 := ourtime.now() +t2.warp('+1h')! +``` + +### Absolute Time + +Supports `YYYY-MM-DD HH:mm:ss`, `YYYY-MM-DD HH:mm`, `YYYY-MM-DD HH`, `YYYY-MM-DD`, `DD-MM-YYYY`. + +```v +t1 := ourtime.new('2022-12-05 20:14:35')! +t2 := ourtime.new('2022-12-05')! // Time defaults to 00:00:00 +``` + +## Methods Overview + +### Creation + +```v +now_time := ourtime.now() +from_string := ourtime.new('2023-01-15')! +from_epoch := ourtime.new_from_epoch(1673788800) +``` + +### Formatting + +```v +mut t := ourtime.now() +println(t.str()) // YYYY-MM-DD HH:mm +println(t.day()) // YYYY-MM-DD +println(t.key()) // YYYY_MM_DD_HH_mm_ss +println(t.md()) // Markdown format +``` + +### Operations + +```v +mut t := ourtime.now() +t.warp('+1h')! // Move 1 hour forward +unix_ts := t.unix() +is_empty := t.empty() +``` + +## Error Handling + +Time parsing methods return a `Result` type and should be handled with `!` or `or` blocks. + +```v +t_valid := ourtime.new('2023-01-01')! +t_invalid := ourtime.new('bad-date') or { + println('Error: ${err}') + ourtime.now() // Fallback +} + +``` + +File: /Users/despiegk/code/github/freeflowuniverse/herolib/aiprompts/herolib_core/core_paths.md +```md +# Pathlib Usage Guide + +## Overview + +The pathlib module provides a comprehensive interface for handling file system operations. Key features include: + +- Robust path handling for files, directories, and symlinks +- Support for both absolute and relative paths +- Automatic home directory expansion (~) +- Recursive directory operations +- Path filtering and listing +- File and directory metadata access + +## Basic Usage + +### Importing pathlib +```v +import freeflowuniverse.herolib.core.pathlib +``` + +### Creating Path Objects +```v +// Create a Path object for a file +mut file_path := pathlib.get("path/to/file.txt") + +// Create a Path object for a directory +mut dir_path := pathlib.get("path/to/directory") +``` + +### Basic Path Operations +```v +// Get absolute path +abs_path := file_path.absolute() + +// Get real path (resolves symlinks) +real_path := file_path.realpath() + +// Check if path exists +if file_path.exists() { + // Path exists +} +``` + +## Path Properties and Methods + +### Path Types +```v +// Check if path is a file +if file_path.is_file() { + // Handle as file +} + +// Check if path is a directory +if dir_path.is_dir() { + // Handle as directory +} + +// Check if path is a symlink +if file_path.is_link() { + // Handle as symlink +} +``` + +### Path Normalization +```v +// Normalize path (remove extra slashes, resolve . and ..) +normalized_path := file_path.path_normalize() + +// Get path directory +dir_path := file_path.path_dir() + +// Get path name without extension +name_no_ext := file_path.name_no_ext() +``` + +## File and Directory Operations + +### File Operations +```v +// Write to file +file_path.write("Content to write")! + +// Read from file +content := file_path.read()! + +// Delete file +file_path.delete()! +``` + +### Directory Operations +```v +// Create directory +mut dir := pathlib.get_dir( + path: "path/to/new/dir" + create: true +)! + +// List directory contents +mut dir_list := dir.list()! + +// Delete directory +dir.delete()! +``` + +### Symlink Operations +```v +// Create symlink +file_path.link("path/to/symlink", delete_exists: true)! + +// Resolve symlink +real_path := file_path.realpath() +``` + +## Advanced Operations + +### Path Copying +```v +// Copy file to destination +file_path.copy(dest: "path/to/destination")! +``` + +### Recursive Operations +```v +// List directory recursively +mut recursive_list := dir.list(recursive: true)! + +// Delete directory recursively +dir.delete()! +``` + +### Path Filtering +```v +// List files matching pattern +mut filtered_list := dir.list( + regex: [r".*\.txt$"], + recursive: true +)! +``` + +## Best Practices + +### Error Handling +```v +if file_path.exists() { + // Safe to operate +} else { + // Handle missing file +} +``` + + +``` + +File: /Users/despiegk/code/github/freeflowuniverse/herolib/aiprompts/herolib_core/core_text.md +```md +# TextTools Module + +The `texttools` module provides a comprehensive set of utilities for text manipulation and processing. + +## Functions and Examples: + +```v +import freeflowuniverse.herolib.core.texttools + +assert hello_world == texttools.name_fix("Hello World!") + +``` +### Name/Path Processing +* `name_fix(name string) string`: Normalizes filenames and paths. +* `name_fix_keepspace(name string) !string`: Like name_fix but preserves spaces. +* `name_fix_no_ext(name_ string) string`: Removes file extension. +* `name_fix_snake_to_pascal(name string) string`: Converts snake_case to PascalCase. + ```v + name := texttools.name_fix_snake_to_pascal("hello_world") // Result: "HelloWorld" + ``` +* `snake_case(name string) string`: Converts PascalCase to snake_case. + ```v + name := texttools.snake_case("HelloWorld") // Result: "hello_world" + ``` +* `name_split(name string) !(string, string)`: Splits name into site and page components. + + +### Text Cleaning +* `name_clean(r string) string`: Normalizes names by removing special characters. + ```v + name := texttools.name_clean("Hello@World!") // Result: "HelloWorld" + ``` +* `ascii_clean(r string) string`: Removes all non-ASCII characters. +* `remove_empty_lines(text string) string`: Removes empty lines from text. + ```v + text := texttools.remove_empty_lines("line1\n\nline2\n\n\nline3") // Result: "line1\nline2\nline3" + ``` +* `remove_double_lines(text string) string`: Removes consecutive empty lines. +* `remove_empty_js_blocks(text string) string`: Removes empty code blocks (```...```). + +### Command Line Parsing +* `cmd_line_args_parser(text string) ![]string`: Parses command line arguments with support for quotes and escaping. + ```v + args := texttools.cmd_line_args_parser("'arg with spaces' --flag=value") // Result: ['arg with spaces', '--flag=value'] + ``` +* `text_remove_quotes(text string) string`: Removes quoted sections from text. +* `check_exists_outside_quotes(text string, items []string) bool`: Checks if items exist in text outside of quotes. + +### Text Expansion +* `expand(txt_ string, l int, expand_with string) string`: Expands text to a specified length with a given character. + +### Indentation +* `indent(text string, prefix string) string`: Adds indentation prefix to each line. + ```v + text := texttools.indent("line1\nline2", " ") // Result: " line1\n line2\n" + ``` +* `dedent(text string) string`: Removes common leading whitespace from every line. + ```v + text := texttools.dedent(" line1\n line2") // Result: "line1\nline2" + ``` + +### String Validation +* `is_int(text string) bool`: Checks if text contains only digits. +* `is_upper_text(text string) bool`: Checks if text contains only uppercase letters. + +### Multiline Processing +* `multiline_to_single(text string) !string`: Converts multiline text to a single line with proper escaping. + +### Text Splitting +* `split_smart(t string, delimiter_ string) []string`: Intelligent string splitting that respects quotes. + +### Tokenization +* `tokenize(text_ string) TokenizerResult`: Tokenizes text into meaningful parts. +* `text_token_replace(text string, tofind string, replacewith string) !string`: Replaces tokens in text. + +### Version Parsing +* `version(text_ string) int`: Converts version strings to comparable integers. + ```v + ver := texttools.version("v0.4.36") // Result: 4036 + ver = texttools.version("v1.4.36") // Result: 1004036 + ``` + +### Formatting +* `format_rfc1123(t time.Time) string`: Formats a time.Time object into RFC 1123 format. + + +### Array Operations +* `to_array(r string) []string`: Converts a comma or newline separated list to an array of strings. + ```v + text := "item1,item2,item3" + array := texttools.to_array(text) // Result: ['item1', 'item2', 'item3'] + ``` +* `to_array_int(r string) []int`: Converts a text list to an array of integers. +* `to_map(mapstring string, line string, delimiter_ string) map[string]string`: Intelligent mapping of a line to a map based on a template. + ```v + r := texttools.to_map("name,-,-,-,-,pid,-,-,-,-,path", + "root 304 0.0 0.0 408185328 1360 ?? S 16Dec23 0:34.06 /usr/sbin/distnoted") + // Result: {'name': 'root', 'pid': '1360', 'path': '/usr/sbin/distnoted'} + ``` + +``` + + +create a module in rust in location packages/core/logger + +which reimplements herolib/lib/core/logger +all features need to be reimplemented + + +write me an implementation plan for my coding agent + + +