ariadna 1.3.0 → 2.0.0

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.
Files changed (149) hide show
  1. checksums.yaml +4 -4
  2. data/ariadna.gemspec +0 -1
  3. data/data/agents/ariadna-codebase-mapper.md +34 -722
  4. data/data/agents/ariadna-debugger.md +44 -1139
  5. data/data/agents/ariadna-executor.md +75 -396
  6. data/data/agents/ariadna-planner.md +78 -1215
  7. data/data/agents/ariadna-roadmapper.md +55 -582
  8. data/data/agents/ariadna-verifier.md +60 -702
  9. data/data/ariadna/templates/config.json +8 -33
  10. data/data/ariadna/workflows/debug.md +28 -0
  11. data/data/ariadna/workflows/execute-phase.md +31 -513
  12. data/data/ariadna/workflows/map-codebase.md +20 -319
  13. data/data/ariadna/workflows/new-milestone.md +20 -365
  14. data/data/ariadna/workflows/new-project.md +19 -880
  15. data/data/ariadna/workflows/plan-phase.md +24 -443
  16. data/data/ariadna/workflows/progress.md +20 -376
  17. data/data/ariadna/workflows/quick.md +19 -221
  18. data/data/ariadna/workflows/roadmap-ops.md +28 -0
  19. data/data/ariadna/workflows/verify-work.md +23 -560
  20. data/data/commands/ariadna/add-phase.md +11 -22
  21. data/data/commands/ariadna/debug.md +11 -143
  22. data/data/commands/ariadna/execute-phase.md +12 -30
  23. data/data/commands/ariadna/insert-phase.md +7 -14
  24. data/data/commands/ariadna/map-codebase.md +16 -49
  25. data/data/commands/ariadna/new-milestone.md +12 -25
  26. data/data/commands/ariadna/new-project.md +22 -26
  27. data/data/commands/ariadna/plan-phase.md +13 -22
  28. data/data/commands/ariadna/progress.md +16 -6
  29. data/data/commands/ariadna/quick.md +9 -11
  30. data/data/commands/ariadna/remove-phase.md +9 -12
  31. data/data/commands/ariadna/verify-work.md +14 -19
  32. data/data/skills/rails-backend/API.md +138 -0
  33. data/data/skills/rails-backend/CONTROLLERS.md +154 -0
  34. data/data/skills/rails-backend/JOBS.md +132 -0
  35. data/data/skills/rails-backend/MODELS.md +213 -0
  36. data/data/skills/rails-backend/SKILL.md +169 -0
  37. data/data/skills/rails-frontend/ASSETS.md +154 -0
  38. data/data/skills/rails-frontend/COMPONENTS.md +253 -0
  39. data/data/skills/rails-frontend/SKILL.md +187 -0
  40. data/data/skills/rails-frontend/VIEWS.md +168 -0
  41. data/data/skills/rails-performance/PROFILING.md +106 -0
  42. data/data/skills/rails-performance/SKILL.md +217 -0
  43. data/data/skills/rails-security/AUDIT.md +118 -0
  44. data/data/skills/rails-security/SKILL.md +422 -0
  45. data/data/skills/rails-testing/FIXTURES.md +78 -0
  46. data/data/skills/rails-testing/SKILL.md +160 -0
  47. data/data/skills/rails-testing/SYSTEM-TESTS.md +73 -0
  48. data/lib/ariadna/installer.rb +11 -15
  49. data/lib/ariadna/tools/cli.rb +0 -12
  50. data/lib/ariadna/tools/config_manager.rb +10 -72
  51. data/lib/ariadna/tools/frontmatter.rb +23 -1
  52. data/lib/ariadna/tools/init.rb +201 -401
  53. data/lib/ariadna/tools/model_profiles.rb +6 -14
  54. data/lib/ariadna/tools/phase_manager.rb +1 -10
  55. data/lib/ariadna/tools/state_manager.rb +170 -451
  56. data/lib/ariadna/tools/template_filler.rb +4 -12
  57. data/lib/ariadna/tools/verification.rb +21 -399
  58. data/lib/ariadna/uninstaller.rb +9 -0
  59. data/lib/ariadna/version.rb +1 -1
  60. data/lib/ariadna.rb +1 -0
  61. metadata +20 -91
  62. data/data/agents/ariadna-backend-executor.md +0 -261
  63. data/data/agents/ariadna-frontend-executor.md +0 -259
  64. data/data/agents/ariadna-integration-checker.md +0 -418
  65. data/data/agents/ariadna-phase-researcher.md +0 -469
  66. data/data/agents/ariadna-plan-checker.md +0 -622
  67. data/data/agents/ariadna-project-researcher.md +0 -618
  68. data/data/agents/ariadna-research-synthesizer.md +0 -236
  69. data/data/agents/ariadna-test-executor.md +0 -266
  70. data/data/ariadna/references/checkpoints.md +0 -772
  71. data/data/ariadna/references/continuation-format.md +0 -249
  72. data/data/ariadna/references/decimal-phase-calculation.md +0 -65
  73. data/data/ariadna/references/git-integration.md +0 -248
  74. data/data/ariadna/references/git-planning-commit.md +0 -38
  75. data/data/ariadna/references/model-profile-resolution.md +0 -32
  76. data/data/ariadna/references/model-profiles.md +0 -73
  77. data/data/ariadna/references/phase-argument-parsing.md +0 -61
  78. data/data/ariadna/references/planning-config.md +0 -194
  79. data/data/ariadna/references/questioning.md +0 -153
  80. data/data/ariadna/references/rails-conventions.md +0 -416
  81. data/data/ariadna/references/tdd.md +0 -267
  82. data/data/ariadna/references/ui-brand.md +0 -160
  83. data/data/ariadna/references/verification-patterns.md +0 -853
  84. data/data/ariadna/templates/codebase/architecture.md +0 -481
  85. data/data/ariadna/templates/codebase/concerns.md +0 -380
  86. data/data/ariadna/templates/codebase/conventions.md +0 -434
  87. data/data/ariadna/templates/codebase/integrations.md +0 -328
  88. data/data/ariadna/templates/codebase/stack.md +0 -189
  89. data/data/ariadna/templates/codebase/structure.md +0 -418
  90. data/data/ariadna/templates/codebase/testing.md +0 -606
  91. data/data/ariadna/templates/context.md +0 -283
  92. data/data/ariadna/templates/continue-here.md +0 -78
  93. data/data/ariadna/templates/debug-subagent-prompt.md +0 -91
  94. data/data/ariadna/templates/phase-prompt.md +0 -609
  95. data/data/ariadna/templates/planner-subagent-prompt.md +0 -117
  96. data/data/ariadna/templates/research-project/ARCHITECTURE.md +0 -439
  97. data/data/ariadna/templates/research-project/FEATURES.md +0 -168
  98. data/data/ariadna/templates/research-project/PITFALLS.md +0 -406
  99. data/data/ariadna/templates/research-project/STACK.md +0 -251
  100. data/data/ariadna/templates/research-project/SUMMARY.md +0 -247
  101. data/data/ariadna/templates/state.md +0 -176
  102. data/data/ariadna/templates/summary-complex.md +0 -59
  103. data/data/ariadna/templates/summary-minimal.md +0 -41
  104. data/data/ariadna/templates/summary-standard.md +0 -48
  105. data/data/ariadna/templates/user-setup.md +0 -310
  106. data/data/ariadna/workflows/add-phase.md +0 -111
  107. data/data/ariadna/workflows/add-todo.md +0 -157
  108. data/data/ariadna/workflows/audit-milestone.md +0 -241
  109. data/data/ariadna/workflows/check-todos.md +0 -176
  110. data/data/ariadna/workflows/complete-milestone.md +0 -644
  111. data/data/ariadna/workflows/diagnose-issues.md +0 -219
  112. data/data/ariadna/workflows/discovery-phase.md +0 -289
  113. data/data/ariadna/workflows/discuss-phase.md +0 -408
  114. data/data/ariadna/workflows/execute-plan.md +0 -448
  115. data/data/ariadna/workflows/help.md +0 -470
  116. data/data/ariadna/workflows/insert-phase.md +0 -129
  117. data/data/ariadna/workflows/list-phase-assumptions.md +0 -178
  118. data/data/ariadna/workflows/pause-work.md +0 -122
  119. data/data/ariadna/workflows/plan-milestone-gaps.md +0 -256
  120. data/data/ariadna/workflows/remove-phase.md +0 -154
  121. data/data/ariadna/workflows/research-phase.md +0 -74
  122. data/data/ariadna/workflows/resume-project.md +0 -306
  123. data/data/ariadna/workflows/set-profile.md +0 -80
  124. data/data/ariadna/workflows/settings.md +0 -145
  125. data/data/ariadna/workflows/transition.md +0 -493
  126. data/data/ariadna/workflows/update.md +0 -212
  127. data/data/ariadna/workflows/verify-phase.md +0 -226
  128. data/data/commands/ariadna/add-todo.md +0 -42
  129. data/data/commands/ariadna/audit-milestone.md +0 -42
  130. data/data/commands/ariadna/check-todos.md +0 -41
  131. data/data/commands/ariadna/complete-milestone.md +0 -136
  132. data/data/commands/ariadna/discuss-phase.md +0 -86
  133. data/data/commands/ariadna/help.md +0 -22
  134. data/data/commands/ariadna/list-phase-assumptions.md +0 -50
  135. data/data/commands/ariadna/pause-work.md +0 -35
  136. data/data/commands/ariadna/plan-milestone-gaps.md +0 -40
  137. data/data/commands/ariadna/reapply-patches.md +0 -110
  138. data/data/commands/ariadna/research-phase.md +0 -187
  139. data/data/commands/ariadna/resume-work.md +0 -40
  140. data/data/commands/ariadna/set-profile.md +0 -34
  141. data/data/commands/ariadna/settings.md +0 -36
  142. data/data/commands/ariadna/update.md +0 -37
  143. data/data/guides/backend.md +0 -3069
  144. data/data/guides/frontend.md +0 -1479
  145. data/data/guides/performance.md +0 -1193
  146. data/data/guides/security.md +0 -1522
  147. data/data/guides/style-guide.md +0 -1091
  148. data/data/guides/testing.md +0 -504
  149. data/data/templates.md +0 -94
@@ -0,0 +1,160 @@
1
+ ---
2
+ name: rails-testing
3
+ description: Ruby on Rails testing conventions — Minitest strategy, fixtures, system tests. Use when writing tests, setting up test data, or running test suites.
4
+ ---
5
+
6
+ # Rails Testing Skill
7
+
8
+ Testing strategy for Rails applications using **Minitest** and **fixtures**. Not RSpec, not FactoryBot.
9
+
10
+ **Sub-files:**
11
+ - [FIXTURES.md](FIXTURES.md) — Fixture patterns and test data management
12
+ - [SYSTEM-TESTS.md](SYSTEM-TESTS.md) — System and integration test conventions
13
+
14
+ ---
15
+
16
+ ## Philosophy
17
+
18
+ - **Test at the model level first** — Business logic lives in models; that's where tests provide the most value
19
+ - **Fixtures over factories** — YAML fixtures are deterministic, fast, and require no setup code
20
+ - **Always set `Current.session`** — Lambda defaults and multi-tenancy depend on it; forgetting it is the #1 test failure
21
+ - **Test behavior, not implementation** — Assert on outcomes, not internal method calls
22
+ - **Use `assert_difference` for state changes** — Catches false positives that boolean checks alone miss
23
+
24
+ ---
25
+
26
+ ## File Organization
27
+
28
+ ```
29
+ test/
30
+ models/
31
+ card/
32
+ archivable_test.rb # Concern tests get their own file
33
+ card_test.rb
34
+ controllers/
35
+ cards/
36
+ closures_controller_test.rb
37
+ jobs/
38
+ notify_recipients_job_test.rb
39
+ fixtures/
40
+ cards.yml
41
+ sessions.yml
42
+ ```
43
+
44
+ ---
45
+
46
+ ## Standard Test Structure
47
+
48
+ ```ruby
49
+ require "test_helper"
50
+
51
+ class Card::CloseableTest < ActiveSupport::TestCase
52
+ setup do
53
+ Current.session = sessions(:david) # Always — sets user + account context
54
+ end
55
+
56
+ test "close creates closure record and marks card closed" do
57
+ card = cards(:logo)
58
+
59
+ assert_difference -> { Closure.count }, +1 do
60
+ card.close
61
+ end
62
+
63
+ assert card.closed?
64
+ end
65
+ end
66
+ ```
67
+
68
+ ---
69
+
70
+ ## Model Testing
71
+
72
+ ### State Changes
73
+
74
+ Nest `assert_difference` blocks when multiple record types change together:
75
+
76
+ ```ruby
77
+ assert_difference -> { Closure.count }, +1 do
78
+ assert_difference -> { Event.count }, +1 do
79
+ card.close
80
+ end
81
+ end
82
+
83
+ assert card.closed?
84
+ assert_equal "card_closed", Event.last.action
85
+ ```
86
+
87
+ ### Event Attributes
88
+
89
+ Count alone is insufficient — always verify attributes:
90
+
91
+ ```ruby
92
+ event = Event.last
93
+ assert_equal "card_archived", event.action
94
+ assert_equal card, event.eventable
95
+ assert_equal Current.user, event.creator
96
+ # For events with particulars:
97
+ assert_equal old_board.name, event.particulars["old_board"]
98
+ ```
99
+
100
+ ### Scopes
101
+
102
+ ```ruby
103
+ card.archive
104
+
105
+ assert_includes Card.archived, card
106
+ assert_not_includes Card.unarchived, card
107
+ ```
108
+
109
+ ---
110
+
111
+ ## Controller Testing
112
+
113
+ Inherit from `ActionDispatch::IntegrationTest`. Assert on model state after the request:
114
+
115
+ ```ruby
116
+ class Cards::ClosuresControllerTest < ActionDispatch::IntegrationTest
117
+ setup do
118
+ Current.session = sessions(:david)
119
+ @card = cards(:logo)
120
+ end
121
+
122
+ test "create closes the card" do
123
+ post card_closure_path(@card.number)
124
+ assert @card.reload.closed?
125
+ end
126
+ end
127
+ ```
128
+
129
+ ---
130
+
131
+ ## Job Testing
132
+
133
+ Test synchronous logic and async enqueuing separately:
134
+
135
+ ```ruby
136
+ # Business logic — fast, no job infrastructure
137
+ test "process_data updates records" do
138
+ record.process_data
139
+ assert record.processed?
140
+ end
141
+
142
+ # Async wrapper — verify enqueuing
143
+ test "process_data_later enqueues job" do
144
+ assert_enqueued_with(job: ProcessDataJob, args: [record]) do
145
+ record.process_data_later
146
+ end
147
+ end
148
+ ```
149
+
150
+ Multi-tenancy is automatic — `Current.account` is captured and restored by the job extension. Setting `Current.session` in `setup` is sufficient.
151
+
152
+ ---
153
+
154
+ ## Common Gotchas
155
+
156
+ **1. Missing `Current.session`** — Lambda defaults (`default: -> { Current.user }`) raise `nil` errors. Put it in `setup`, not inline.
157
+
158
+ **2. Only checking event count** — After `assert_difference`, verify `Event.last.action`, `eventable`, and `creator`.
159
+
160
+ **3. Skipping `assert_difference`** — `assert card.archived?` can pass if the flag is set wrong. Wrap the action in `assert_difference -> { Card::Archive.count }, +1` to confirm the record was created.
@@ -0,0 +1,73 @@
1
+ # System Tests & Integration Tests
2
+
3
+ Part of the [Rails Testing Skill](SKILL.md).
4
+
5
+ ---
6
+
7
+ ## When to Write System Tests
8
+
9
+ System tests (browser-driven via Capybara) are reserved for **critical user flows**:
10
+
11
+ - Authentication and session management
12
+ - Multi-step form submissions
13
+ - JavaScript-dependent interactions (Turbo streams, Stimulus)
14
+ - Flows that span multiple controllers
15
+
16
+ For business logic, prefer model tests — faster and more precise.
17
+
18
+ ---
19
+
20
+ ## Test Type Selection
21
+
22
+ | Concern | Use |
23
+ |---|---|
24
+ | Business logic, state changes | Model test (`ActiveSupport::TestCase`) |
25
+ | Controller routing, response codes | `ActionDispatch::IntegrationTest` |
26
+ | Full browser flow, JS interactions | System test (`ApplicationSystemTestCase`) |
27
+ | Job enqueuing | `assert_enqueued_with` in model/controller test |
28
+
29
+ ---
30
+
31
+ ## System Test Structure
32
+
33
+ ```ruby
34
+ require "application_system_test_case"
35
+
36
+ class CardLifecycleTest < ApplicationSystemTestCase
37
+ setup do
38
+ sign_in_as users(:david)
39
+ end
40
+
41
+ test "user can close and reopen a card" do
42
+ visit card_path(cards(:logo))
43
+ click_on "Close"
44
+ assert_text "Card closed"
45
+
46
+ click_on "Reopen"
47
+ assert_text "Card reopened"
48
+ end
49
+ end
50
+ ```
51
+
52
+ Define `sign_in_as` in `ApplicationSystemTestCase` — do not repeat the login flow in each test.
53
+
54
+ ---
55
+
56
+ ## Integration Tests (Controller-Level)
57
+
58
+ For HTTP assertions without a browser, set `Current.session` directly:
59
+
60
+ ```ruby
61
+ class Cards::ClosuresControllerTest < ActionDispatch::IntegrationTest
62
+ setup do
63
+ Current.session = sessions(:david)
64
+ end
65
+
66
+ test "POST creates closure and redirects" do
67
+ post card_closure_path(cards(:logo).number)
68
+ assert_redirected_to card_path(cards(:logo).number)
69
+ end
70
+ end
71
+ ```
72
+
73
+ No browser sign-in flow needed — `Current.session` assignment is sufficient for controller tests.
@@ -21,7 +21,7 @@ module Ariadna
21
21
 
22
22
  copy_commands
23
23
  copy_agents
24
- copy_guides
24
+ copy_skills
25
25
  copy_content
26
26
  write_version
27
27
  install_statusline
@@ -125,7 +125,7 @@ module Ariadna
125
125
 
126
126
  def source_manifest_keys
127
127
  keys = []
128
- %w[commands/ariadna agents guides ariadna].each do |subdir|
128
+ %w[commands/ariadna agents skills ariadna].each do |subdir|
129
129
  src_base = subdir == "agents" ? source_dir : source_dir
130
130
  dir = File.join(src_base, subdir)
131
131
  next unless File.directory?(dir)
@@ -142,7 +142,7 @@ module Ariadna
142
142
  end
143
143
 
144
144
  def cleanup_empty_dirs
145
- %w[commands/ariadna agents guides ariadna].each do |subdir|
145
+ %w[commands/ariadna agents skills ariadna].each do |subdir|
146
146
  dir = File.join(@target_dir, subdir)
147
147
  next unless File.directory?(dir)
148
148
 
@@ -201,23 +201,19 @@ module Ariadna
201
201
  puts " \u2713 Installed #{count} agents"
202
202
  end
203
203
 
204
- def copy_guides
205
- src = File.join(source_dir, "guides")
206
- dest = File.join(@target_dir, "guides")
207
- FileUtils.mkdir_p(dest)
208
-
209
- Dir[File.join(src, "*.md")].each do |file|
210
- FileUtils.cp(file, File.join(dest, File.basename(file)))
211
- end
212
- count = Dir[File.join(dest, "*.md")].size
213
- puts " \u2713 Installed #{count} guides"
204
+ def copy_skills
205
+ src = File.join(source_dir, "skills")
206
+ dest = File.join(@target_dir, "skills")
207
+ copy_tree(src, dest)
208
+ count = Dir[File.join(dest, "*")].count { |d| File.directory?(d) }
209
+ puts " \u2713 Installed #{count} skills"
214
210
  end
215
211
 
216
212
  def copy_content
217
213
  src = File.join(source_dir, "ariadna")
218
214
  dest = File.join(@target_dir, "ariadna")
219
215
  copy_tree(src, dest)
220
- puts " \u2713 Installed ariadna/ (workflows, templates, references)"
216
+ puts " \u2713 Installed ariadna/ (workflows, templates)"
221
217
  end
222
218
 
223
219
  def write_version
@@ -240,7 +236,7 @@ module Ariadna
240
236
 
241
237
  def generate_manifest_entries
242
238
  entries = {}
243
- %w[commands/ariadna agents guides ariadna].each do |subdir|
239
+ %w[commands/ariadna agents skills ariadna].each do |subdir|
244
240
  dir = File.join(@target_dir, subdir)
245
241
  next unless File.directory?(dir)
246
242
 
@@ -30,9 +30,6 @@ module Ariadna
30
30
  when "verify-path-exists"
31
31
  require_relative "utilities"
32
32
  Utilities.verify_path_exists(argv, raw: raw)
33
- when "verify-summary"
34
- require_relative "verification"
35
- Verification.verify_summary(argv, raw: raw)
36
33
  when "config-ensure-section"
37
34
  require_relative "config_manager"
38
35
  ConfigManager.ensure_section(argv, raw: raw)
@@ -42,12 +39,6 @@ module Ariadna
42
39
  when "history-digest"
43
40
  require_relative "state_manager"
44
41
  StateManager.history_digest(argv, raw: raw)
45
- when "summary-extract"
46
- require_relative "state_manager"
47
- StateManager.summary_extract(argv, raw: raw)
48
- when "state-snapshot"
49
- require_relative "state_manager"
50
- StateManager.snapshot(argv, raw: raw)
51
42
  when "phase-plan-index"
52
43
  require_relative "phase_manager"
53
44
  PhaseManager.plan_index(argv, raw: raw)
@@ -63,9 +54,6 @@ module Ariadna
63
54
  when "milestone"
64
55
  require_relative "phase_manager"
65
56
  PhaseManager.milestone_dispatch(argv, raw: raw)
66
- when "validate"
67
- require_relative "verification"
68
- Verification.validate_dispatch(argv, raw: raw)
69
57
  when "progress"
70
58
  require_relative "roadmap_analyzer"
71
59
  RoadmapAnalyzer.progress(argv, raw: raw)
@@ -7,70 +7,25 @@ module Ariadna
7
7
  module ConfigManager
8
8
  DEFAULTS = {
9
9
  "model_profile" => "balanced",
10
- "commit_docs" => true,
11
- "search_gitignored" => false,
10
+ "verifier" => true,
12
11
  "branching_strategy" => "none",
13
12
  "phase_branch_template" => "ariadna/phase-{phase}-{slug}",
14
13
  "milestone_branch_template" => "ariadna/{milestone}-{slug}",
15
- "research" => false,
16
- "plan_checker" => true,
17
- "verifier" => true,
18
- "parallelization" => true,
19
- "execution_mode" => "vertical",
20
- "team_execution" => false
14
+ "commit_docs" => true,
15
+ "search_gitignored" => false,
16
+ "parallelization" => true
21
17
  }.freeze
22
18
 
23
19
  def self.load_config(cwd = Dir.pwd)
24
20
  config_path = File.join(cwd, ".ariadna_planning", "config.json")
25
21
  return DEFAULTS.dup unless File.exist?(config_path)
26
22
 
27
- raw = File.read(config_path)
28
- parsed = JSON.parse(raw)
29
-
30
- get = lambda do |key, nested = nil|
31
- return parsed[key] if parsed.key?(key)
32
-
33
- if nested && parsed[nested[:section]].is_a?(Hash)
34
- val = parsed[nested[:section]][nested[:field]]
35
- return val unless val.nil?
36
- end
37
- nil
38
- end
39
-
40
- parallelization = begin
41
- val = get.call("parallelization")
42
- if val.is_a?(Hash) && val.key?("enabled")
43
- val["enabled"]
44
- elsif [true, false].include?(val)
45
- val
46
- else
47
- DEFAULTS["parallelization"]
48
- end
23
+ parsed = JSON.parse(File.read(config_path))
24
+ result = DEFAULTS.dup
25
+ DEFAULTS.each_key do |key|
26
+ result[key] = parsed[key] unless parsed[key].nil?
49
27
  end
50
-
51
- # nil_or helper: use default only when value is nil (preserves false)
52
- nil_or = ->(val, default) { val.nil? ? default : val }
53
-
54
- {
55
- "model_profile" => get.call("model_profile") || DEFAULTS["model_profile"],
56
- "commit_docs" => nil_or.call(get.call("commit_docs", { section: "planning", field: "commit_docs" }), DEFAULTS["commit_docs"]),
57
- "search_gitignored" => nil_or.call(get.call("search_gitignored", { section: "planning", field: "search_gitignored" }), DEFAULTS["search_gitignored"]),
58
- "branching_strategy" => get.call("branching_strategy", { section: "git", field: "branching_strategy" }) || DEFAULTS["branching_strategy"],
59
- "phase_branch_template" => get.call("phase_branch_template", { section: "git", field: "phase_branch_template" }) || DEFAULTS["phase_branch_template"],
60
- "milestone_branch_template" => get.call("milestone_branch_template", { section: "git", field: "milestone_branch_template" }) || DEFAULTS["milestone_branch_template"],
61
- "research" => nil_or.call(get.call("research", { section: "workflow", field: "research" }), DEFAULTS["research"]),
62
- "plan_checker" => nil_or.call(get.call("plan_checker", { section: "workflow", field: "plan_check" }), DEFAULTS["plan_checker"]),
63
- "verifier" => nil_or.call(get.call("verifier", { section: "workflow", field: "verifier" }), DEFAULTS["verifier"]),
64
- "parallelization" => parallelization,
65
- "execution_mode" => get.call("execution_mode", { section: "execution", field: "mode" }) || DEFAULTS["execution_mode"],
66
- "team_execution" => begin
67
- val = get.call("team_execution", { section: "execution", field: "team" })
68
- case val
69
- when "auto", true, false then val
70
- else DEFAULTS["team_execution"]
71
- end
72
- end
73
- }
28
+ result
74
29
  rescue JSON::ParserError
75
30
  DEFAULTS.dup
76
31
  end
@@ -87,24 +42,7 @@ module Ariadna
87
42
  return
88
43
  end
89
44
 
90
- defaults = {
91
- model_profile: "balanced",
92
- commit_docs: true,
93
- search_gitignored: false,
94
- branching_strategy: "none",
95
- phase_branch_template: "ariadna/phase-{phase}-{slug}",
96
- milestone_branch_template: "ariadna/{milestone}-{slug}",
97
- workflow: {
98
- research: false,
99
- plan_check: true,
100
- verifier: true
101
- },
102
- parallelization: true,
103
- execution_mode: "vertical",
104
- team_execution: false
105
- }
106
-
107
- File.write(config_path, JSON.pretty_generate(defaults))
45
+ File.write(config_path, JSON.pretty_generate(DEFAULTS))
108
46
  Output.json({ created: true, path: ".ariadna_planning/config.json" }, raw: raw, raw_value: "created")
109
47
  end
110
48
 
@@ -7,9 +7,15 @@ module Ariadna
7
7
  SCHEMAS = {
8
8
  "plan" => %w[phase plan type],
9
9
  "summary" => %w[phase plan subsystem],
10
- "verification" => %w[phase]
10
+ "verification" => %w[phase],
11
+ "skill" => %w[name description]
11
12
  }.freeze
12
13
 
14
+ SKILL_RESERVED_WORDS = %w[anthropic claude].freeze
15
+ SKILL_NAME_PATTERN = /\A[a-z][a-z0-9-]*\z/
16
+ SKILL_NAME_MAX = 64
17
+ SKILL_DESCRIPTION_MAX = 1024
18
+
13
19
  def self.dispatch(argv, raw: false)
14
20
  subcommand = argv.shift
15
21
  file = argv.shift
@@ -191,6 +197,22 @@ module Ariadna
191
197
  Output.error("File not found: #{file}")
192
198
  end
193
199
 
200
+ def self.validate_skill(fm)
201
+ errors = []
202
+ name = fm["name"].to_s
203
+ description = fm["description"].to_s
204
+
205
+ errors << "name must match lowercase-hyphens pattern" unless name.match?(SKILL_NAME_PATTERN)
206
+ errors << "name must be #{SKILL_NAME_MAX} chars or fewer" if name.length > SKILL_NAME_MAX
207
+ if SKILL_RESERVED_WORDS.any? { |w| name.include?(w) }
208
+ errors << "name contains reserved word"
209
+ end
210
+ errors << "description must not be empty" if description.empty?
211
+ errors << "description must be #{SKILL_DESCRIPTION_MAX} chars or fewer" if description.length > SKILL_DESCRIPTION_MAX
212
+
213
+ errors
214
+ end
215
+
194
216
  # --- Private helpers ---
195
217
 
196
218
  def self.parse_yaml(yaml_str)