aia 1.0.0 → 1.1.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
data/docs/index.md CHANGED
@@ -23,80 +23,48 @@ Welcome to AIA, your powerful CLI tool for dynamic prompt management and AI inte
23
23
 
24
24
  ---
25
25
 
26
- !!! danger "Breaking Changes in v1.0.0 (from v0.11.2)"
26
+ !!! tip "🚀 New: AI Assistant Scheduler (AIAS)"
27
27
 
28
- **Version 1.0.0 is a major release with breaking changes. Read this section before upgrading.**
28
+ **Schedule and automate your AIA prompts!** AIAS is a new Ruby gem that lets you run AIA prompts on a cron-like schedule — perfect for recurring AI tasks, automated reports, and timed workflows.
29
29
 
30
- !!! failure "Prompt File Format (Critical)"
30
+ **[View AIAS on GitHub →](https://github.com/madbomber/aias)**
31
31
 
32
- The prompt file format has changed completely. You **must** migrate your prompts.
33
-
34
- - **File extension** — `.txt` is now `.md`
35
- - **Parameters** — `[PLACEHOLDER]` is now `<%= placeholder %>`
36
- - **Metadata** — Separate `.json` history files are now YAML front matter inside the `.md` file
37
- - **Directive embedding** — `//config`, `//pipeline`, etc. are now YAML front matter keys
38
-
39
- **Before (v0.11.2):**
40
-
41
- # ~/.prompts/my_prompt.txt
42
- //config temperature 0.7
43
- //pipeline next_prompt, another_prompt
44
- Tell me about [TOPIC] in [LANGUAGE]
45
-
46
- **After (v1.0.0):**
47
-
48
- ---
49
- temperature: 0.7
50
- pipeline: [next_prompt, another_prompt]
51
- parameters:
52
- topic: null
53
- language: null
54
- ---
55
- Tell me about <%= topic %> in <%= language %>
56
-
57
- **Run the migration tool:**
58
-
59
- # Back up first (recommended)
60
- cp -r ~/.prompts ~/.prompts.backup
61
-
62
- # Migrate all prompts
63
- migrate_prompts --verbose ~/.prompts
64
-
65
- # Review flagged files (*.txt-review), then recover them
66
- migrate_prompts --reprocess ~/.prompts
67
-
68
- The `migrate_prompts` script is a standalone program in `bin/`. It handles placeholder conversion, directive migration, history file merging, and flags files with code fences for manual review. See [Migration Guide](guides/migrate-prompts.md) for details.
32
+ ---
69
33
 
70
- !!! warning "Removed and Deprecated CLI Options"
34
+ !!! tip "🎭 Roles: Give Your Robot a Personality"
71
35
 
72
- - **`--adapter` removed** AIA now exclusively uses `ruby_llm`. Remove `--adapter` from any scripts or aliases.
73
- - **`--regex` deprecated** — Parameter extraction uses ERB (`<%= param %>`) instead of regex. The flag prints a warning and has no effect.
74
- - **`--terse` deprecated** — No longer supported. The flag prints a warning and has no effect.
75
- - **`--metrics` renamed to `--tokens`** — Use `--tokens` to display token usage. `--cost` implies `--tokens`.
36
+ Roles are plain-text prompt files that define how your AI thinks, talks, and interacts with you. Drop one in `~/.prompts/roles/` and your robot instantly becomes someone new.
76
37
 
77
- !!! warning "Dependency: prompt_manager v0.5 to v1.0"
38
+ **For fun:**
39
+ ```bash
40
+ aia --chat --role pirate # Arrr, matey!
41
+ aia --chat --role nyc_cabbie # opinions about EVERYTHING
42
+ aia --chat --role stoned_hacker # solves it anyway, dude
43
+ ```
78
44
 
79
- The `prompt_manager` gem has been upgraded from `~> 0.5.8` to `~> 1.0`. This is the root cause of the prompt file format changes above. The new version uses ERB for parameter interpolation and YAML front matter for metadata. All prompt files must be in the new format.
45
+ **For serious work:**
46
+ ```bash
47
+ # Explains quantum physics to your 7-year-old
48
+ aia --chat --role first_grade_teacher
80
49
 
81
- !!! info "Environment Variables (from v0.10.0)"
50
+ # Three expert robots on the same design doc, simultaneously
51
+ aia --model gpt-4o=architect,claude=security,gemini=performance design.md
52
+ ```
82
53
 
83
- If upgrading from v0.9.x, environment variable names changed in v0.10.0 to use double underscore (`__`) for nested config sections:
54
+ Assign a different role to each model and get multiple expert perspectives in one command. **[Full Roles Guide →](guides/models.md)**
84
55
 
85
- | Old (v0.9.x) | New (v1.0.0) |
86
- |---|---|
87
- | `AIA_PROMPTS_DIR` | `AIA_PROMPTS__DIR` |
88
- | `AIA_OUT_FILE` | `AIA_OUTPUT__FILE` |
89
- | `AIA_VERBOSE` | `AIA_FLAGS__VERBOSE` |
90
- | `AIA_DEBUG` | `AIA_FLAGS__DEBUG` |
91
- | `AIA_CHAT` | `AIA_FLAGS__CHAT` |
92
- | `AIA_TEMPERATURE` | `AIA_LLM__TEMPERATURE` |
93
- | `AIA_MARKDOWN` | `AIA_OUTPUT__MARKDOWN` |
56
+ !!! tip "🎓 Skills: Teach Your Robot Your Process"
94
57
 
95
- `AIA_MODEL` is unchanged. See [Environment Variables](configuration.md#environment-variables) for the complete list.
58
+ Skills are structured instruction sets that tell your robot *exactly how* to approach a task — your workflow, your standards, every single time. Each skill is a directory with a `SKILL.md` file in `~/.prompts/skills/`.
96
59
 
97
- !!! info "Configuration Files (from v0.10.0)"
60
+ ```bash
61
+ aia -s code-quality my_prompt # one skill
62
+ aia -s code-quality,security-review my_prompt # stack them
63
+ aia --chat --role senior_dev -s code-quality # role + skill
64
+ /skill code-quality # add mid-chat
65
+ ```
98
66
 
99
- If upgrading from v0.9.x, configuration files now use a nested YAML structure with sections (`llm:`, `prompts:`, `output:`, `flags:`, etc.) and follow the XDG Base Directory Specification (`~/.config/aia/aia.yml`). See [Configuration Guide](configuration.md) for details.
67
+ Combine roles and skills: a pirate who follows your code review process, a first-grade teacher who uses your step-by-step explanation method, or multiple robots each with their own role and skill set all from the command line. **[Full Skills Guide](directives-reference.md)**
100
68
 
101
69
  ---
102
70
 
@@ -105,9 +73,15 @@ Welcome to AIA, your powerful CLI tool for dynamic prompt management and AI inte
105
73
  ### 🚀 Dynamic Prompt Management
106
74
  - **Hierarchical Configuration**: Embedded directives > CLI args > environment variables > config files > defaults
107
75
  - **Prompt Sequences and Workflows**: Chain prompts together for complex AI workflows
108
- - **Role-based Prompts**: Use predefined roles to context your AI interactions
109
76
  - **Fuzzy Search**: Find prompts quickly with fuzzy matching (requires `fzf`)
110
77
 
78
+ ### 🎭 Roles & 🎓 Skills
79
+ - **Robot Personalities**: Give any robot a voice — fun personas like a pirate or NYC cabbie, or professional ones like a senior architect or first-grade teacher
80
+ - **Per-Model Roles**: Assign a different role to each model in a multi-model session — `--model gpt-4o=architect,claude=security`
81
+ - **Reusable Skill Sets**: Encode your exact workflow once in a `SKILL.md` file; apply it to any prompt with `-s skill-name`
82
+ - **Role + Skill Combos**: A robot can be *who you want* (role) and *know exactly what to do* (skill) simultaneously
83
+ - **Mid-Chat Skills**: Add a skill to a running chat session with `/skill skill-name`
84
+
111
85
  ### 🔧 Powerful Integration
112
86
  - **Shell Integration**: Execute shell commands directly within prompts
113
87
  - **Ruby (ERB) Processing**: Use Ruby code in your prompts for dynamic content
@@ -8,10 +8,15 @@ AIA provides sophisticated prompt management capabilities through the PM gem, en
8
8
  ```
9
9
  ~/.prompts/
10
10
  ├── README.md # Documentation for your prompt collection
11
- ├── roles/ # Role-based prompts for context setting
11
+ ├── roles/ # Role definitions (LLM personality/persona)
12
12
  │ ├── assistant.md
13
13
  │ ├── code_expert.md
14
14
  │ └── teacher.md
15
+ ├── skills/ # Skill definitions (task instructions)
16
+ │ ├── code-review/
17
+ │ │ └── SKILL.md # YAML front matter + instruction body
18
+ │ └── summarizer/
19
+ │ └── SKILL.md
15
20
  ├── development/ # Development-related prompts
16
21
  │ ├── code_review.md
17
22
  │ ├── debug_help.md
@@ -292,6 +297,88 @@ Current Task:
292
297
  Please provide guidance consistent with the project architecture and your role as <%= role %>.
293
298
  ```
294
299
 
300
+ ## Skills
301
+
302
+ ### Roles vs Skills
303
+
304
+ These two concepts work together but serve distinct purposes:
305
+
306
+ | Concept | Defines | Loaded from | Injected as |
307
+ |---------|---------|-------------|-------------|
308
+ | **Role** | LLM *personality* — who the model is | `~/.prompts/roles/<id>.md` | First, before skills and prompt |
309
+ | **Skill** | Task *instructions* — how to approach the work | `~/.prompts/skills/<name>/SKILL.md` | After role, before user prompt |
310
+
311
+ A **role** sets the persona: "You are a senior Ruby developer with deep expertise in performance optimization."
312
+
313
+ A **skill** provides procedural guidance for that persona to follow when executing the user's request: "When reviewing code, always check for: N+1 queries, missing indexes, memory leaks, and security vulnerabilities. Present findings as a prioritized list."
314
+
315
+ The assembled prompt order is:
316
+
317
+ ```
318
+ 1. Role content ← WHO the LLM is (personality)
319
+ 2. Skill content(s) ← HOW to approach the task (instructions)
320
+ 3. User prompt ← WHAT to do (request)
321
+ 4. Context files ← supporting material
322
+ ```
323
+
324
+ ### Skill File Format
325
+
326
+ Each skill lives in its own subdirectory under `~/.prompts/skills/`. The subdirectory must contain a `SKILL.md` file with YAML front matter followed by the skill instruction body:
327
+
328
+ ```markdown
329
+ ---
330
+ name: code-review
331
+ description: Thorough code review focusing on correctness, security, and maintainability.
332
+ user-invocable: true
333
+ argument-hint: ["file or topic to review"]
334
+ ---
335
+
336
+ When reviewing code, systematically check:
337
+
338
+ 1. **Correctness** — Does the logic match the stated intent? Are edge cases handled?
339
+ 2. **Security** — Are there injection risks, unsafe deserialization, or exposed secrets?
340
+ 3. **Performance** — Are there N+1 queries, unbounded loops, or unnecessary allocations?
341
+ 4. **Maintainability** — Is the code readable? Are names clear? Is complexity justified?
342
+
343
+ Present findings as a prioritized list with file:line references where applicable.
344
+ Always suggest a concrete fix, not just identification of the problem.
345
+ ```
346
+
347
+ The YAML front matter is metadata only. Only the body (everything after the closing `---`) is injected into the prompt.
348
+
349
+ ### Using Skills
350
+
351
+ ```bash
352
+ # Prepend a skill before the user prompt
353
+ aia --skill code-review review_prompt my_code.rb
354
+
355
+ # Combine role + skill for maximum context
356
+ aia --role ruby_expert --skill code-review review_prompt my_code.rb
357
+
358
+ # Multiple skills (applied in order)
359
+ aia --skill code-review --skill security-audit review_prompt my_code.rb
360
+ aia -s code-review,security-audit review_prompt my_code.rb
361
+
362
+ # List available skills
363
+ aia --list-skills
364
+
365
+ # Use a skill from within a chat session
366
+ /skill code-review
367
+ ```
368
+
369
+ ### Skills in Chat Mode
370
+
371
+ In chat mode, use the `/skill` directive to inject a skill at any point in the conversation:
372
+
373
+ ```
374
+ > /skill summarizer
375
+ [Skill "summarizer" instructions are injected into the next message context]
376
+
377
+ > Please summarize the discussion so far.
378
+ ```
379
+
380
+ The `/skill` directive injects only the body content of `SKILL.md` — the YAML front matter is never sent to the LLM.
381
+
295
382
  ## Prompt Workflows and Pipelines
296
383
 
297
384
  ### Simple Workflows
data/examples/common.sh CHANGED
@@ -18,3 +18,10 @@ while IFS= read -r var; do
18
18
  done < <(env | grep '^AIA_' | cut -d= -f1)
19
19
 
20
20
  CONFIG="aia_config.yml"
21
+
22
+ # When running from the develop branch, prefer the local bin/aia over any
23
+ # installed gem so that uncommitted working-tree changes are exercised.
24
+ LOCAL_AIA="${SCRIPT_DIR}/../bin/aia"
25
+ if [[ -x "${LOCAL_AIA}" ]]; then
26
+ export PATH="${SCRIPT_DIR}/../bin:${PATH}"
27
+ fi
@@ -4,9 +4,9 @@ temperature: 0.3
4
4
  ---
5
5
  Summarize this project based on the following information:
6
6
 
7
- Gemspec name: <%= `grep 'spec.name' ../../*.gemspec 2>/dev/null`.strip %>
7
+ Gemspec name: <%= `grep 'spec.name' ../*.gemspec 2>/dev/null`.strip %>
8
8
  Ruby version: <%= RUBY_VERSION %>
9
9
  Current directory: $(pwd)
10
- Files in lib/: $(ls ../../lib/aia/*.rb 2>/dev/null | wc -l | tr -d ' ')
10
+ Files in lib/: $(ls ../lib/aia/*.rb 2>/dev/null | wc -l | tr -d ' ')
11
11
 
12
12
  Provide a three-sentence overview.
data/lib/aia/chat_loop.rb CHANGED
@@ -6,6 +6,8 @@ require "pm"
6
6
 
7
7
  module AIA
8
8
  class ChatLoop
9
+ include AIA::SkillUtils
10
+
9
11
  def initialize(chat_processor, ui_presenter, directive_processor)
10
12
  @chat_processor = chat_processor
11
13
  @ui_presenter = ui_presenter
@@ -15,6 +17,8 @@ module AIA
15
17
  # Start the interactive chat session
16
18
  def start(skip_context_files: false)
17
19
  setup_session
20
+ process_role_context
21
+ process_skill_context
18
22
  process_initial_context(skip_context_files)
19
23
  handle_piped_input
20
24
  run_loop
@@ -39,6 +43,63 @@ module AIA
39
43
  Signal.trap("INT") { exit }
40
44
  end
41
45
 
46
+ def process_role_context
47
+ role = AIA.config.prompts.role
48
+ return if role.nil? || role.empty?
49
+
50
+ prompt_handler = AIA::PromptHandler.new
51
+
52
+ # fetch_role applies the role file's metadata (including any model: key) to
53
+ # AIA.config. Preserve the user's chosen model so the chat session keeps
54
+ # using the model from the CLI, not one embedded in the role file.
55
+ saved_models = AIA.config.models
56
+ role_parsed = prompt_handler.fetch_role(role)
57
+ AIA.config.models = saved_models
58
+
59
+ return if role_parsed.nil?
60
+
61
+ role_content = role_parsed.to_s
62
+ return if role_content.nil? || role_content.strip.empty?
63
+
64
+ return unless AIA.client.respond_to?(:chats)
65
+
66
+ system_msg = RubyLLM::Message.new(role: :system, content: role_content)
67
+
68
+ AIA.client.chats.each_value do |chat|
69
+ next if chat.messages.any? { |m| m.role == :system }
70
+ chat.add_message(system_msg)
71
+ end
72
+ end
73
+
74
+ def process_skill_context
75
+ skills = AIA.config.prompts.skills
76
+ return if skills.nil? || skills.empty?
77
+
78
+ skills_dir = AIA.config.skills.dir
79
+ bodies = Array(skills).filter_map do |skill_name|
80
+ skill_name = skill_name.to_s.strip
81
+ next if skill_name.empty?
82
+
83
+ skill_path = find_skill_dir(skill_name, skills_dir)
84
+ next unless skill_path
85
+
86
+ if File.file?(skill_path)
87
+ skill_body(File.read(skill_path))
88
+ else
89
+ md = File.join(skill_path, 'SKILL.md')
90
+ skill_body(File.read(md)) if File.exist?(md)
91
+ end
92
+ end
93
+
94
+ return if bodies.empty?
95
+
96
+ skill_content = bodies.join("\n\n")
97
+ response_data = @chat_processor.process_prompt(skill_content)
98
+ content = response_data.is_a?(Hash) ? response_data[:content] : response_data
99
+ @chat_processor.output_response(content)
100
+ @ui_presenter.display_separator
101
+ end
102
+
42
103
  def process_initial_context(skip_context_files)
43
104
  return if skip_context_files || !AIA.config.context_files || AIA.config.context_files.empty?
44
105
 
@@ -98,7 +98,24 @@ module AIA
98
98
  first_model = models.first
99
99
  model_name = first_model.respond_to?(:name) ? first_model.name : first_model.to_s
100
100
 
101
- unless model_name.downcase.include?(client_model.downcase)
101
+ # client_model is the full resolved ID (e.g. "claude-sonnet-4-20250514"),
102
+ # model_name is the configured alias (e.g. "claude-sonnet-4") or a
103
+ # provider-prefixed name (e.g. "ollama/qwen3").
104
+ #
105
+ # The adapter strips provider prefixes when creating the RubyLLM chat
106
+ # (e.g. "ollama/qwen3" → model: "qwen3"), so client_model has no prefix.
107
+ # Strip the prefix from model_name before comparing so that
108
+ # "qwen3".include?("qwen3") → true (was: "qwen3".include?("ollama/qwen3") → false).
109
+ comparable_name = model_name.sub(%r{\A[^/]+/}, '')
110
+ unless client_model.downcase.include?(comparable_name.downcase)
111
+ # Never replace the adapter when conversation history exists — doing so
112
+ # destroys all prior context. Role/prompt files can change AIA.config.models
113
+ # via front matter metadata, but that must not evict an active chat session.
114
+ if AIA.client.respond_to?(:chats) &&
115
+ AIA.client.chats.values.any? { |chat| chat.messages.any? }
116
+ warn "Warning: Model config changed to '#{model_name}' but keeping '#{client_model}' to preserve conversation history."
117
+ return
118
+ end
102
119
  AIA.client = AIA.client.class.new
103
120
  end
104
121
  end
@@ -6,7 +6,9 @@
6
6
  # for the Config class.
7
7
 
8
8
  require 'optparse'
9
+ require 'yaml'
9
10
  require_relative 'model_spec'
11
+ require_relative '../skill_utils'
10
12
 
11
13
  module AIA
12
14
  module CLIParser
@@ -166,6 +168,23 @@ module AIA
166
168
  warn "Warning: --regex is deprecated. PM v1.0.0 uses ERB parameters (<%= param %>)."
167
169
  options[:parameter_regex] = pattern
168
170
  end
171
+
172
+ opts.on("--skills-dir DIR", "Set directory containing skill subdirectories") do |dir|
173
+ options[:skills_dir] = dir
174
+ end
175
+
176
+ opts.on("--skills-prefix PREFIX", "Set subdirectory name for skill files (default: skills)") do |prefix|
177
+ options[:skills_prefix] = prefix
178
+ end
179
+
180
+ opts.on("-s", "--skill SKILL_IDS", "Prepend skill(s) to prompt (comma-separated IDs or paths)") do |ids|
181
+ options[:skills] ||= []
182
+ options[:skills] += ids.split(',').map(&:strip)
183
+ end
184
+
185
+ opts.on("--list-skills", "List available skills and exit") do
186
+ options[:list_skills] = true
187
+ end
169
188
  end
170
189
 
171
190
  def setup_ai_parameters(opts, options)
@@ -410,6 +429,13 @@ module AIA
410
429
  end
411
430
 
412
431
  def validate_role_exists(role_id)
432
+ if AIA::SkillUtils.path_based_id?(role_id)
433
+ expanded = File.expand_path(role_id)
434
+ expanded += '.md' if File.extname(expanded).empty?
435
+ raise ArgumentError, "Role file not found: #{expanded}" unless File.exist?(expanded)
436
+ return
437
+ end
438
+
413
439
  prompts_dir = ENV.fetch('AIA_PROMPTS__DIR', File.join(ENV['HOME'], '.prompts'))
414
440
  roles_prefix = ENV.fetch('AIA_PROMPTS__ROLES_PREFIX', 'roles')
415
441
 
@@ -442,19 +468,30 @@ module AIA
442
468
  roles_prefix = ENV.fetch('AIA_PROMPTS__ROLES_PREFIX', 'roles')
443
469
  roles_dir = File.join(prompts_dir, roles_prefix)
444
470
 
445
- if Dir.exist?(roles_dir)
446
- roles = list_available_role_names(prompts_dir, roles_prefix)
447
-
448
- if roles.empty?
449
- puts "No role files found in #{roles_dir}"
450
- puts "Create .md files in this directory to define roles."
451
- else
452
- puts "Available roles in #{roles_dir}:"
453
- roles.each { |role| puts " - #{role}" }
454
- end
455
- else
471
+ unless Dir.exist?(roles_dir)
456
472
  puts "No roles directory found at #{roles_dir}"
457
473
  puts "Create this directory and add role files to use roles."
474
+ return
475
+ end
476
+
477
+ roles = list_available_role_names(prompts_dir, roles_prefix)
478
+
479
+ if roles.empty?
480
+ puts "No role files found in #{roles_dir}"
481
+ puts "Create .md files in this directory to define roles."
482
+ return
483
+ end
484
+
485
+ roles.each do |role_id|
486
+ role_file = File.join(roles_dir, "#{role_id}.md")
487
+ fm = AIA::SkillUtils.parse_front_matter(role_file)
488
+
489
+ puts "## #{role_id}"
490
+ puts
491
+ puts "| Key | Value |"
492
+ puts "|-----|-------|"
493
+ fm.each { |key, value| puts "| #{key} | #{value} |" }
494
+ puts
458
495
  end
459
496
  end
460
497
 
@@ -464,6 +501,7 @@ module AIA
464
501
 
465
502
  Dir.glob("**/*.md", base: roles_dir)
466
503
  .map { |f| f.chomp('.md') }
504
+ .reject { |f| f.split('/').any? { |part| part.start_with?('_') } }
467
505
  .sort
468
506
  end
469
507
 
@@ -59,12 +59,26 @@ defaults:
59
59
  prompts:
60
60
  dir: ~/.prompts
61
61
  extname: .md
62
- roles_prefix: roles
63
- roles_dir: ~/.prompts/roles
62
+ #
64
63
  role: ~
64
+ roles_dir: ~/.prompts/roles
65
+ roles_prefix: roles
66
+ #
67
+ skills: []
68
+ skills_prefix: skills
69
+ #
70
+ tool: ~
71
+ tools_prefix: tools
72
+ #
65
73
  system_prompt: ~
66
74
  parameter_regex: ~
67
75
 
76
+ roles:
77
+ dir: ~/.prompts/roles
78
+
79
+ skills:
80
+ dir: ~/.prompts/skills
81
+
68
82
  # ---------------------------------------------------------------------------
69
83
  # Output Configuration
70
84
  # Access: AIA.config.output.file, AIA.config.output.append, etc.
@@ -112,6 +126,7 @@ defaults:
112
126
  # Env: AIA_TOOLS__PATHS, AIA_TOOLS__ALLOWED, etc.
113
127
  # ---------------------------------------------------------------------------
114
128
  tools:
129
+ dir: ~/.prompts/tools
115
130
  paths: []
116
131
  allowed: ~
117
132
  rejected: ~
@@ -2,6 +2,7 @@
2
2
 
3
3
  require 'word_wrapper'
4
4
  require_relative '../adapter/gem_activator'
5
+ require_relative '../skill_utils'
5
6
 
6
7
  # lib/aia/config/validator.rb
7
8
  #
@@ -23,6 +24,10 @@ module AIA
23
24
  stdin_content = process_stdin_content
24
25
  config.stdin_content = stdin_content if stdin_content && !stdin_content.strip.empty?
25
26
 
27
+ # Require tool files early so AIA::Directive subclasses defined in them
28
+ # are registered into PM.directives before PromptHandler.new is called.
29
+ require_tool_files(config)
30
+
26
31
  # Process arguments and validate
27
32
  process_prompt_id_from_args(config, remaining_args)
28
33
  validate_and_set_context_files(config, remaining_args)
@@ -31,6 +36,7 @@ module AIA
31
36
  handle_dump_config(config)
32
37
  handle_mcp_list(config)
33
38
  handle_list_tools(config)
39
+ handle_list_skills(config)
34
40
  handle_completion_script(config)
35
41
  validate_required_prompt_id(config)
36
42
  process_role_configuration(config)
@@ -128,14 +134,18 @@ module AIA
128
134
  return if role.nil? || role.empty?
129
135
 
130
136
  roles_prefix = config.prompts.roles_prefix
131
- unless roles_prefix.nil? || roles_prefix.empty?
132
- unless role.start_with?(roles_prefix)
133
- config.prompts.role = "#{roles_prefix}/#{role}"
134
- role = config.prompts.role
135
- end
137
+
138
+ unless AIA::SkillUtils.path_based_id?(role) || roles_prefix.nil? || roles_prefix.empty? || role.start_with?(roles_prefix)
139
+ config.prompts.role = "#{roles_prefix}/#{role}"
140
+ role = config.prompts.role
136
141
  end
137
142
 
138
- config.prompts.roles_dir ||= File.join(config.prompts.dir, roles_prefix)
143
+ config.prompts.roles_dir ||= File.join(config.prompts.dir, roles_prefix.to_s)
144
+
145
+ # In chat-only mode (no prompt_id), leave the role configured so ChatLoop
146
+ # can inject it as initial context. Promoting it to prompt_id would cause
147
+ # PM to receive the role path as a literal string rather than file content.
148
+ return if config.flags&.chat == true
139
149
 
140
150
  if config.prompt_id.nil? || config.prompt_id.empty?
141
151
  unless role.nil? || role.empty?
@@ -230,6 +240,40 @@ module AIA
230
240
  exit 0
231
241
  end
232
242
 
243
+ def handle_list_skills(config)
244
+ return unless config.list_skills
245
+
246
+ skills_dir = AIA.config.skills.dir
247
+
248
+ unless Dir.exist?(skills_dir)
249
+ $stderr.puts "No skills directory found at #{skills_dir}"
250
+ exit 0
251
+ end
252
+
253
+ skill_dirs = Dir.glob("*/SKILL.md", base: skills_dir).map { |f| File.dirname(f) }.sort
254
+
255
+ if skill_dirs.empty?
256
+ $stderr.puts "No skills found in #{skills_dir}"
257
+ exit 0
258
+ end
259
+
260
+ skill_dirs.each do |skill_name|
261
+ skill_md = File.join(skills_dir, skill_name, 'SKILL.md')
262
+ fm = AIA::SkillUtils.parse_front_matter(skill_md)
263
+
264
+ puts "## #{skill_name}"
265
+ puts
266
+ puts "| Key | Value |"
267
+ puts "|-----|-------|"
268
+ fm.each do |key, value|
269
+ puts "| #{key} | #{value} |"
270
+ end
271
+ puts
272
+ end
273
+
274
+ exit 0
275
+ end
276
+
233
277
  def list_tools_terminal(local_tools, mcp_tool_groups)
234
278
  width = (ENV['COLUMNS'] || 80).to_i - 4
235
279
  indent = ' '
@@ -321,6 +365,19 @@ module AIA
321
365
  !Array(config.mcp_use).empty? || !Array(config.mcp_skip).empty?
322
366
  end
323
367
 
368
+ def require_tool_files(config)
369
+ Array(config.tools&.paths).each do |path|
370
+ expanded = File.expand_path(path)
371
+ if File.exist?(expanded)
372
+ require expanded
373
+ else
374
+ warn "Warning: Tool file not found: #{path}"
375
+ end
376
+ rescue LoadError, StandardError => e
377
+ warn "Warning: Failed to load tool '#{path}': #{e.message}"
378
+ end
379
+ end
380
+
324
381
  def load_local_tools(config)
325
382
  # Load required libraries (with gem activation and lazy-load triggering)
326
383
  Array(config.require_libs).each do |lib|
@@ -518,6 +575,7 @@ module AIA
518
575
  File.write(file, content)
519
576
  puts "Config successfully dumped to #{file}"
520
577
  end
578
+
521
579
  end
522
580
  end
523
581
  end