diff --git a/docker/postgresql/test/Dockerfile b/docker/postgresql/test/Dockerfile
new file mode 100644
index 00000000..e69de29b
diff --git a/docker/postgresql/test/file.test b/docker/postgresql/test/file.test
new file mode 100644
index 00000000..e69de29b
diff --git a/examples/develop/heroprompt/heroprompt_example.vsh b/examples/develop/heroprompt/heroprompt_example.vsh
index bee22d7c..1b1c2edc 100755
--- a/examples/develop/heroprompt/heroprompt_example.vsh
+++ b/examples/develop/heroprompt/heroprompt_example.vsh
@@ -20,14 +20,10 @@ dir2.select_file(name: 'build.sh')!
dir2.select_file(name: 'debug.sh')!
mut dir3 := workspace1.add_dir(
- path: '/Users/mahmoud/code/github/freeflowuniverse/herolib/docker/postgresql'
- select_all_dirs: true
- select_all_files: false
- select_all: false
+ path: '/Users/mahmoud/code/github/freeflowuniverse/herolib/docker/postgresql'
+ select_all: true
)!
-dir3.select_file(name: 'docker-compose.yml')!
-
selected := workspace1.get_selected()
prompt := workspace1.prompt(
diff --git a/lib/biz/bizmodel/templates/product.md b/lib/biz/bizmodel/templates/product.md
index 9789f3b5..9f88638a 100644
--- a/lib/biz/bizmodel/templates/product.md
+++ b/lib/biz/bizmodel/templates/product.md
@@ -9,7 +9,7 @@
Product ${name1} has revenue events (one offs)
-@{model.sheet.wiki() or {''}}
+@{model.sheet.wiki() or {''}}
namefilter:'${name1}_revenue,${name1}_cogs,${name1}_cogs_perc,${name1}_maintenance_month_perc' sheetname:'bizmodel_tf9
- COGS = Cost of Goods Sold (is our cost to deliver the product/service)
@@ -21,14 +21,14 @@ Product ${name1} has revenue events (one offs)
Product sold and its revenue/cost of goods
-@{model.sheet.wiki() or {''}}
+@{model.sheet.wiki() or {''}}
namefilter:'${name1}_nr_sold,${name1}_revenue_setup,${name1}_revenue_monthly,${name1}_cogs_setup,${name1}_cogs_setup_perc,${name1}_cogs_monthly,${name1}_cogs_monthly_perc'
sheetname:'bizmodel_tf9
- nr sold, is the nr sold per month of ${name1}
- revenue setup is setup per item for ${name1}, this is the money we receive. Similar there is a revenue monthly.
- cogs = Cost of Goods Sold (is our cost to deliver the product)
- - can we as a setup per item, or per month per item
+ - can we as a setup per item, or per month per item
@if product.nr_months_recurring>1
@@ -40,23 +40,22 @@ This product ${name1} is recurring, means customer pays per month ongoing, the p
#### the revenue/cogs calculated
-@{model.sheet.wiki() or {''}}
+@{model.sheet.wiki() or {''}}
namefilter:'${name1}_nr_sold_recurring'
sheetname:'bizmodel_tf9
This results in following revenues and cogs:
-@{model.sheet.wiki() or {''}}
+@{model.sheet.wiki() or {''}}
namefilter:'${name1}_revenue_setup_total,${name1}_revenue_monthly_total,${name1}_cogs_setup_total,${name1}_cogs_monthly_total,${name1}_cogs_setup_from_perc,${name1}_cogs_monthly_from_perc,${name1}_maintenance_month,
${name1}_revenue_monthly_recurring,${name1}_cogs_monthly_recurring'
sheetname:'bizmodel_tf9
resulting revenues:
-@{model.sheet.wiki() or {''}}
+@{model.sheet.wiki() or {''}}
namefilter:'${name1}_revenue_total,${name1}_cogs_total'
sheetname:'bizmodel_tf9
-
!!!spreadsheet.graph_line_row rowname:'${name1}_cogs_total' unit:million sheetname:'bizmodel_tf9'
!!!spreadsheet.graph_line_row rowname:'${name1}_revenue_total' unit:million sheetname:'bizmodel_tf9'
diff --git a/lib/develop/heroprompt/heroprompt_dir.v b/lib/develop/heroprompt/heroprompt_dir.v
index 0ce43742..88245978 100644
--- a/lib/develop/heroprompt/heroprompt_dir.v
+++ b/lib/develop/heroprompt/heroprompt_dir.v
@@ -3,12 +3,14 @@ module heroprompt
import os
import freeflowuniverse.herolib.core.pathlib
+// Parameters for adding a file to a directory
@[params]
pub struct AddFileParams {
pub mut:
- name string
+ name string // Name of the file to select
}
+// select_file adds a specific file to the directory's selected files list
pub fn (mut dir HeropromptDir) select_file(args AddFileParams) !&HeropromptFile {
mut full_path := dir.path.path + '/' + args.name
if dir.path.path.ends_with('/') {
@@ -38,3 +40,46 @@ pub fn (mut dir HeropromptDir) select_file(args AddFileParams) !&HeropromptFile
dir.files << file
return file
}
+
+// select_all_files_and_dirs recursively selects all files and subdirectories
+// from the given path and adds them to the current directory structure
+pub fn (mut dir HeropromptDir) select_all_files_and_dirs(path string) {
+ // First, get all immediate children (files and directories) of the current path
+ entries := os.ls(path) or { return }
+
+ for entry in entries {
+ full_path := os.join_path(path, entry)
+
+ if os.is_dir(full_path) {
+ // Create subdirectory
+ mut sub_dir := &HeropromptDir{
+ path: pathlib.Path{
+ path: full_path
+ cat: .dir
+ exist: .yes
+ }
+ name: entry
+ }
+
+ // Recursively populate the subdirectory
+ sub_dir.select_all_files_and_dirs(full_path)
+
+ // Add subdirectory to current directory
+ dir.dirs << sub_dir
+ } else if os.is_file(full_path) {
+ // Read file content when selecting all
+ file_content := os.read_file(full_path) or { '' }
+
+ file := &HeropromptFile{
+ path: pathlib.Path{
+ path: full_path
+ cat: .file
+ exist: .yes
+ }
+ name: entry
+ content: file_content
+ }
+ dir.files << file
+ }
+ }
+}
diff --git a/lib/develop/heroprompt/heroprompt_file.v b/lib/develop/heroprompt/heroprompt_file.v
index b8da43fe..d52f0348 100644
--- a/lib/develop/heroprompt/heroprompt_file.v
+++ b/lib/develop/heroprompt/heroprompt_file.v
@@ -1 +1,59 @@
module heroprompt
+
+// Utility function to get file extension with special handling for common files
+pub fn get_file_extension(filename string) string {
+ // Handle special cases for common files without extensions
+ special_files := {
+ 'dockerfile': 'dockerfile'
+ 'makefile': 'makefile'
+ 'license': 'license'
+ 'readme': 'readme'
+ 'changelog': 'changelog'
+ 'authors': 'authors'
+ 'contributors': 'contributors'
+ 'copying': 'copying'
+ 'install': 'install'
+ 'news': 'news'
+ 'todo': 'todo'
+ 'version': 'version'
+ 'manifest': 'manifest'
+ 'gemfile': 'gemfile'
+ 'rakefile': 'rakefile'
+ 'procfile': 'procfile'
+ 'vagrantfile': 'vagrantfile'
+ }
+
+ // Convert to lowercase for comparison
+ lower_filename := filename.to_lower()
+
+ // Check if it's a special file without extension
+ if lower_filename in special_files {
+ return special_files[lower_filename]
+ }
+
+ // Handle dotfiles (files starting with .)
+ if filename.starts_with('.') && !filename.starts_with('..') {
+ // For files like .gitignore, .bashrc, etc.
+ if filename.contains('.') && filename.len > 1 {
+ parts := filename[1..].split('.')
+ if parts.len >= 2 {
+ return parts[parts.len - 1]
+ } else {
+ // Files like .gitignore, .bashrc (treat the whole name as extension type)
+ return filename[1..]
+ }
+ } else {
+ // Single dot files
+ return filename[1..]
+ }
+ }
+
+ // Regular files with extensions
+ parts := filename.split('.')
+ if parts.len < 2 {
+ // Files with no extension - return empty string
+ return ''
+ }
+
+ return parts[parts.len - 1]
+}
diff --git a/lib/develop/heroprompt/heroprompt_session.v b/lib/develop/heroprompt/heroprompt_session.v
index 32d4a817..b3c03469 100644
--- a/lib/develop/heroprompt/heroprompt_session.v
+++ b/lib/develop/heroprompt/heroprompt_session.v
@@ -2,12 +2,14 @@ module heroprompt
import rand
+// HeropromptSession manages multiple workspaces for organizing AI prompts
pub struct HeropromptSession {
pub mut:
- id string
- workspaces []&HeropromptWorkspace
+ id string // Unique session identifier
+ workspaces []&HeropromptWorkspace // List of workspaces in this session
}
+// new_session creates a new heroprompt session with a unique ID
pub fn new_session() HeropromptSession {
return HeropromptSession{
id: rand.uuid_v4()
@@ -15,6 +17,7 @@ pub fn new_session() HeropromptSession {
}
}
+// add_workspace creates and adds a new workspace to the session
pub fn (mut self HeropromptSession) add_workspace(args_ NewWorkspaceParams) !&HeropromptWorkspace {
mut wsp := &HeropromptWorkspace{}
wsp = wsp.new(args_)!
diff --git a/lib/develop/heroprompt/heroprompt_workspace.v b/lib/develop/heroprompt/heroprompt_workspace.v
index 2e7c5677..8ab92470 100644
--- a/lib/develop/heroprompt/heroprompt_workspace.v
+++ b/lib/develop/heroprompt/heroprompt_workspace.v
@@ -5,11 +5,13 @@ import time
import os
import freeflowuniverse.herolib.core.pathlib
+// HeropromptWorkspace represents a workspace containing multiple directories
+// and their selected files for AI prompt generation
@[heap]
pub struct HeropromptWorkspace {
pub mut:
- name string = 'default'
- dirs []&HeropromptDir
+ name string = 'default' // Workspace name
+ dirs []&HeropromptDir // List of directories in this workspace
}
@[params]
@@ -33,7 +35,8 @@ fn (wsp HeropromptWorkspace) new(args_ NewWorkspaceParams) !&HeropromptWorkspace
@[params]
pub struct AddDirParams {
pub mut:
- path string @[required]
+ path string @[required]
+ select_all bool
}
pub fn (mut wsp HeropromptWorkspace) add_dir(args_ AddDirParams) !&HeropromptDir {
@@ -51,7 +54,7 @@ pub fn (mut wsp HeropromptWorkspace) add_dir(args_ AddDirParams) !&HeropromptDir
parts := abs_path.split(os.path_separator)
dir_name := parts[parts.len - 1]
- added_dir := &HeropromptDir{
+ mut added_dir := &HeropromptDir{
path: pathlib.Path{
path: abs_path
cat: .dir
@@ -60,25 +63,30 @@ pub fn (mut wsp HeropromptWorkspace) add_dir(args_ AddDirParams) !&HeropromptDir
name: dir_name
}
+ if args_.select_all {
+ added_dir.select_all_files_and_dirs(abs_path)
+ }
+
wsp.dirs << added_dir
return added_dir
}
+// Metadata structures for selected files and directories
struct SelectedFilesMetadata {
- content_length int
- extension string
- name string
- path string
+ content_length int // File content length in characters
+ extension string // File extension
+ name string // File name
+ path string // Full file path
}
struct SelectedDirsMetadata {
- name string
- selected_files []SelectedFilesMetadata
+ name string // Directory name
+ selected_files []SelectedFilesMetadata // Files in this directory
}
struct HeropromptWorkspaceGetSelected {
pub mut:
- dirs []SelectedDirsMetadata
+ dirs []SelectedDirsMetadata // All directories with their selected files
}
pub fn (wsp HeropromptWorkspace) get_selected() HeropromptWorkspaceGetSelected {
@@ -119,7 +127,8 @@ fn (wsp HeropromptWorkspace) build_user_instructions(text string) string {
return text
}
-fn build_file_map(dirs []&HeropromptDir, prefix string) string {
+// build_file_tree creates a tree-like representation of directories and files
+fn build_file_tree(dirs []&HeropromptDir, prefix string) string {
mut out := ''
for i, dir in dirs {
@@ -129,36 +138,194 @@ fn build_file_map(dirs []&HeropromptDir, prefix string) string {
// Directory name
out += '${prefix}${connector}${dir.name}\n'
+ // Calculate new prefix for children
+ child_prefix := if i == dirs.len - 1 { prefix + ' ' } else { prefix + '│ ' }
+
+ // Count total children (files + subdirs) for proper tree formatting
+ total_children := dir.files.len + dir.dirs.len
+
// Files in this directory
for j, file in dir.files {
- file_connector := if j == dir.files.len - 1 && dir.dirs.len == 0 {
+ file_connector := if j == total_children - 1 { '└── ' } else { '├── ' }
+ out += '${child_prefix}${file_connector}${file.name} *\n'
+ }
+
+ // Recurse into subdirectories
+ for j, sub_dir in dir.dirs {
+ sub_connector := if dir.files.len + j == total_children - 1 {
'└── '
} else {
'├── '
}
- out += '${prefix} ${file_connector}${file.name} *\n'
- }
+ out += '${child_prefix}${sub_connector}${sub_dir.name}\n'
- // Recurse into subdirectories
- if dir.dirs.len > 0 {
- new_prefix := if i == dirs.len - 1 { prefix + ' ' } else { prefix + '│ ' }
- out += build_file_map(dir.dirs, new_prefix)
+ // Recursive call for subdirectory contents
+ sub_prefix := if dir.files.len + j == total_children - 1 {
+ child_prefix + ' '
+ } else {
+ child_prefix + '│ '
+ }
+
+ // Build content for this subdirectory directly without calling build_file_map again
+ sub_total_children := sub_dir.files.len + sub_dir.dirs.len
+
+ // Files in subdirectory
+ for k, sub_file in sub_dir.files {
+ sub_file_connector := if k == sub_total_children - 1 {
+ '└── '
+ } else {
+ '├── '
+ }
+ out += '${sub_prefix}${sub_file_connector}${sub_file.name} *\n'
+ }
+
+ // Recursively handle deeper subdirectories
+ if sub_dir.dirs.len > 0 {
+ out += build_file_tree(sub_dir.dirs, sub_prefix)
+ }
}
}
return out
}
+// build_file_content generates formatted content for all selected files
fn (wsp HeropromptWorkspace) build_file_content() string {
- return ''
+ mut content := ''
+
+ for dir in wsp.dirs {
+ // Process files in current directory
+ for file in dir.files {
+ if content.len > 0 {
+ content += '\n\n'
+ }
+
+ // File path
+ content += '${file.path.path}\n'
+
+ // File content with syntax highlighting or empty file info
+ extension := get_file_extension(file.name)
+ if file.content.len == 0 {
+ content += '(Empty file)\n'
+ } else {
+ content += '```${extension}\n'
+ content += file.content
+ content += '\n```'
+ }
+ }
+
+ // Recursively process subdirectories
+ content += wsp.build_dir_file_content(dir.dirs)
+ }
+
+ return content
}
+// build_dir_file_content recursively processes subdirectories
+fn (wsp HeropromptWorkspace) build_dir_file_content(dirs []&HeropromptDir) string {
+ mut content := ''
+
+ for dir in dirs {
+ // Process files in current directory
+ for file in dir.files {
+ if content.len > 0 {
+ content += '\n\n'
+ }
+
+ // File path
+ content += '${file.path.path}\n'
+
+ // File content with syntax highlighting or empty file info
+ extension := get_file_extension(file.name)
+ if file.content.len == 0 {
+ content += '(Empty file)\n'
+ } else {
+ content += '```${extension}\n'
+ content += file.content
+ content += '\n```'
+ }
+ }
+
+ // Recursively process subdirectories
+ if dir.dirs.len > 0 {
+ content += wsp.build_dir_file_content(dir.dirs)
+ }
+ }
+
+ return content
+}
+
+pub struct HeropromptTmpPrompt {
+pub mut:
+ user_instructions string
+ file_map string
+ file_contents string
+}
+
+// build_prompt generates the final prompt with metadata and file tree
fn (wsp HeropromptWorkspace) build_prompt(text string) string {
user_instructions := wsp.build_user_instructions(text)
- file_map := build_file_map(wsp.dirs, '')
+ file_map := wsp.build_file_map()
file_contents := wsp.build_file_content()
- // Handle reading the prompt file and parse it
+ prompt := HeropromptTmpPrompt{
+ user_instructions: user_instructions
+ file_map: file_map
+ file_contents: file_contents
+ }
+
+ reprompt := $tmpl('./templates/prompt.template')
+ return reprompt
+}
+
+// build_file_map creates a complete file map with base path and metadata
+fn (wsp HeropromptWorkspace) build_file_map() string {
+ mut file_map := ''
+ if wsp.dirs.len > 0 {
+ // Get the common base path from the first directory
+ base_path := wsp.dirs[0].path.path
+ // Find the parent directory of the base path
+ parent_path := if base_path.contains('/') {
+ base_path.split('/')[..base_path.split('/').len - 1].join('/')
+ } else {
+ base_path
+ }
+
+ // Calculate metadata
+ selected_metadata := wsp.get_selected()
+ mut total_files := 0
+ mut total_content_length := 0
+ mut file_extensions := map[string]int{}
+
+ for dir_meta in selected_metadata.dirs {
+ total_files += dir_meta.selected_files.len
+ for file_meta in dir_meta.selected_files {
+ total_content_length += file_meta.content_length
+ if file_meta.extension.len > 0 {
+ file_extensions[file_meta.extension] = file_extensions[file_meta.extension] + 1
+ }
+ }
+ }
+
+ // Build metadata summary
+ mut extensions_summary := ''
+ for ext, count in file_extensions {
+ if extensions_summary.len > 0 {
+ extensions_summary += ', '
+ }
+ extensions_summary += '${ext}(${count})'
+ }
+
+ // Build header with metadata
+ file_map = '${parent_path}\n'
+ file_map += '# Selected Files: ${total_files} | Total Content: ${total_content_length} chars'
+ if extensions_summary.len > 0 {
+ file_map += ' | Extensions: ${extensions_summary}'
+ }
+ file_map += '\n\n'
+ file_map += build_file_tree(wsp.dirs, '')
+ }
+
return file_map
}
@@ -208,12 +375,3 @@ fn generate_random_workspace_name() string {
return '${adj}_${noun}_${number}'
}
-
-fn get_file_extension(filename string) string {
- parts := filename.split('.')
- if parts.len < 2 {
- // Handle the files with no exe such as Dockerfile, LICENSE
- return ''
- }
- return parts[parts.len - 1]
-}
diff --git a/lib/develop/heroprompt/reprompt_actions.v b/lib/develop/heroprompt/reprompt_actions.v
index 4d4f357b..3e82df0b 100644
--- a/lib/develop/heroprompt/reprompt_actions.v
+++ b/lib/develop/heroprompt/reprompt_actions.v
@@ -1,16 +1,13 @@
module heroprompt
-import freeflowuniverse.herolib.data.paramsparser
-import freeflowuniverse.herolib.data.encoderhero
-import freeflowuniverse.herolib.core.pathlib
-import os
-
-// your checking & initialization code if needed
+// TODO: Implement template-based prompt generation
fn (mut ws HeropromptWorkspace) heroprompt() !string {
// TODO: fill in template based on selection
return ''
}
+// TODO: Implement tree visualization utilities
pub fn get_tree() {}
+// TODO: Implement prompt formatting utilities
pub fn format_prompt() {}
diff --git a/lib/develop/heroprompt/templates/prompt.heroprompt b/lib/develop/heroprompt/templates/prompt.heroprompt
deleted file mode 100644
index 251c06c6..00000000
--- a/lib/develop/heroprompt/templates/prompt.heroprompt
+++ /dev/null
@@ -1,11 +0,0 @@
-
-{{text}}
-
-
-
-{{map}}
-
-
-
-{{content}}
-
\ No newline at end of file
diff --git a/lib/develop/heroprompt/templates/prompt.md b/lib/develop/heroprompt/templates/prompt.md
deleted file mode 100644
index 6fdcc7e2..00000000
--- a/lib/develop/heroprompt/templates/prompt.md
+++ /dev/null
@@ -1 +0,0 @@
-TODO:...
\ No newline at end of file
diff --git a/lib/develop/heroprompt/templates/prompt.template b/lib/develop/heroprompt/templates/prompt.template
new file mode 100644
index 00000000..f497141a
--- /dev/null
+++ b/lib/develop/heroprompt/templates/prompt.template
@@ -0,0 +1,11 @@
+
+@{prompt.user_instructions}
+
+
+
+@{prompt.file_map}
+
+
+
+@{prompt.file_contents}
+
\ No newline at end of file