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
@@ -4,19 +4,22 @@ require_relative "output"
4
4
  require_relative "config_manager"
5
5
  require_relative "model_profiles"
6
6
  require_relative "frontmatter"
7
+ require_relative "state_manager"
7
8
 
8
9
  module Ariadna
9
10
  module Tools
10
- module Init
11
- def self.dispatch(argv, raw: false)
11
+ # Lightweight workflow initialization: returns paths, metadata, and config.
12
+ module Init # rubocop:disable Metrics/ModuleLength
13
+ def self.dispatch(argv, raw: false) # rubocop:disable Metrics/CyclomaticComplexity
12
14
  workflow = argv.shift
13
- includes = parse_include_flag(argv)
14
15
 
15
16
  case workflow
16
17
  when "execute-phase"
17
- execute_phase(argv.first, includes, raw: raw)
18
+ execute_phase(argv.first, raw: raw)
18
19
  when "plan-phase"
19
- plan_phase(argv.first, includes, raw: raw)
20
+ plan_phase(argv.first, raw: raw)
21
+ when "verify-work"
22
+ verify_work(argv.first, raw: raw)
20
23
  when "new-project"
21
24
  new_project(raw: raw)
22
25
  when "new-milestone"
@@ -25,8 +28,6 @@ module Ariadna
25
28
  quick(argv.join(" "), raw: raw)
26
29
  when "resume"
27
30
  resume(raw: raw)
28
- when "verify-work"
29
- verify_work(argv.first, raw: raw)
30
31
  when "phase-op"
31
32
  phase_op(argv.first, raw: raw)
32
33
  when "todos"
@@ -36,125 +37,80 @@ module Ariadna
36
37
  when "map-codebase"
37
38
  map_codebase(raw: raw)
38
39
  when "progress"
39
- init_progress(includes, raw: raw)
40
+ init_progress(raw: raw)
40
41
  else
41
- Output.error("Unknown init workflow: #{workflow}\nAvailable: execute-phase, plan-phase, new-project, new-milestone, quick, resume, verify-work, phase-op, todos, milestone-op, map-codebase, progress")
42
+ Output.error("Unknown init workflow: #{workflow}")
42
43
  end
43
44
  end
44
45
 
45
- def self.execute_phase(phase, includes, raw: false)
46
+ def self.execute_phase(phase, raw: false)
46
47
  Output.error("phase required for init execute-phase") unless phase
47
48
 
48
49
  cwd = Dir.pwd
49
50
  config = ConfigManager.load_config(cwd)
50
- phase_info = find_phase_internal(cwd, phase)
51
- milestone = get_milestone_info(cwd)
52
-
53
- branch_name = if config["branching_strategy"] == "phase" && phase_info
54
- config["phase_branch_template"]
55
- .gsub("{phase}", phase_info[:phase_number])
56
- .gsub("{slug}", phase_info[:phase_slug] || "phase")
57
- elsif config["branching_strategy"] == "milestone"
58
- config["milestone_branch_template"]
59
- .gsub("{milestone}", milestone[:version])
60
- .gsub("{slug}", generate_slug(milestone[:name]) || "milestone")
61
- end
62
-
63
- result = {
51
+ phase_info = find_phase(cwd, phase)
52
+ plans = build_plan_metadata(cwd, phase_info)
53
+ incomplete = incomplete_plan_files(cwd, phase_info)
54
+
55
+ result = base_result(config).merge(
64
56
  executor_model: resolve_model(cwd, "ariadna-executor"),
65
- verifier_model: resolve_model(cwd, "ariadna-verifier"),
66
- commit_docs: config["commit_docs"],
67
- parallelization: config["parallelization"],
68
- branching_strategy: config["branching_strategy"],
69
- phase_branch_template: config["phase_branch_template"],
70
- milestone_branch_template: config["milestone_branch_template"],
71
- verifier_enabled: config["verifier"],
72
57
  phase_found: !phase_info.nil?,
73
58
  phase_dir: phase_info&.dig(:directory),
74
59
  phase_number: phase_info&.dig(:phase_number),
75
60
  phase_name: phase_info&.dig(:phase_name),
76
- phase_slug: phase_info&.dig(:phase_slug),
77
- plans: phase_info&.dig(:plans) || [],
78
- summaries: phase_info&.dig(:summaries) || [],
79
- incomplete_plans: phase_info&.dig(:incomplete_plans) || [],
80
- plan_count: phase_info&.dig(:plans)&.length || 0,
81
- incomplete_count: phase_info&.dig(:incomplete_plans)&.length || 0,
82
- branch_name: branch_name,
83
- milestone_version: milestone[:version],
84
- milestone_name: milestone[:name],
85
- milestone_slug: generate_slug(milestone[:name]),
86
- team_execution: config["team_execution"],
87
- execution_mode: config["execution_mode"],
88
- backend_executor_model: resolve_model(cwd, "ariadna-backend-executor"),
89
- frontend_executor_model: resolve_model(cwd, "ariadna-frontend-executor"),
90
- test_executor_model: resolve_model(cwd, "ariadna-test-executor"),
91
- state_exists: path_exists?(cwd, ".ariadna_planning/STATE.md"),
92
- roadmap_exists: path_exists?(cwd, ".ariadna_planning/ROADMAP.md"),
93
- config_exists: path_exists?(cwd, ".ariadna_planning/config.json")
94
- }
95
-
96
- result[:state_content] = safe_read_file(File.join(cwd, ".ariadna_planning", "STATE.md")) if includes.include?("state")
97
- result[:config_content] = safe_read_file(File.join(cwd, ".ariadna_planning", "config.json")) if includes.include?("config")
98
- result[:roadmap_content] = safe_read_file(File.join(cwd, ".ariadna_planning", "ROADMAP.md")) if includes.include?("roadmap")
61
+ plans: plans,
62
+ plan_count: plans.length,
63
+ incomplete_plans: incomplete,
64
+ incomplete_count: incomplete.length,
65
+ milestone_version: milestone_version(cwd)
66
+ )
99
67
 
100
68
  Output.json(result, raw: raw)
101
69
  end
102
70
 
103
- def self.plan_phase(phase, includes, raw: false)
71
+ def self.plan_phase(phase, raw: false) # rubocop:disable Metrics/CyclomaticComplexity
104
72
  Output.error("phase required for init plan-phase") unless phase
105
73
 
106
74
  cwd = Dir.pwd
107
75
  config = ConfigManager.load_config(cwd)
108
- phase_info = find_phase_internal(cwd, phase)
76
+ phase_info = find_phase(cwd, phase)
109
77
 
110
- result = {
111
- researcher_model: resolve_model(cwd, "ariadna-phase-researcher"),
78
+ result = base_result(config).merge(
112
79
  planner_model: resolve_model(cwd, "ariadna-planner"),
113
- checker_model: resolve_model(cwd, "ariadna-plan-checker"),
114
- research_enabled: config["research"],
115
- plan_checker_enabled: config["plan_checker"],
116
- commit_docs: config["commit_docs"],
117
80
  phase_found: !phase_info.nil?,
118
81
  phase_dir: phase_info&.dig(:directory),
119
82
  phase_number: phase_info&.dig(:phase_number),
120
83
  phase_name: phase_info&.dig(:phase_name),
121
- phase_slug: phase_info&.dig(:phase_slug),
122
84
  padded_phase: phase_info&.dig(:phase_number)&.rjust(2, "0"),
123
85
  has_research: phase_info&.dig(:has_research) || false,
124
86
  has_context: phase_info&.dig(:has_context) || false,
125
- has_plans: (phase_info&.dig(:plans)&.length || 0) > 0,
126
- plan_count: phase_info&.dig(:plans)&.length || 0,
127
- planning_exists: path_exists?(cwd, ".ariadna_planning"),
128
- roadmap_exists: path_exists?(cwd, ".ariadna_planning/ROADMAP.md")
129
- }
87
+ plan_count: phase_info&.dig(:plans)&.length || 0
88
+ )
130
89
 
131
- result[:state_content] = safe_read_file(File.join(cwd, ".ariadna_planning", "STATE.md")) if includes.include?("state")
132
- result[:roadmap_content] = safe_read_file(File.join(cwd, ".ariadna_planning", "ROADMAP.md")) if includes.include?("roadmap")
133
- result[:requirements_content] = safe_read_file(File.join(cwd, ".ariadna_planning", "REQUIREMENTS.md")) if includes.include?("requirements")
90
+ Output.json(result, raw: raw)
91
+ end
134
92
 
135
- if includes.include?("context") && phase_info&.dig(:directory)
136
- phase_dir_full = File.join(cwd, phase_info[:directory])
137
- context_file = Dir.children(phase_dir_full).find { |f| f.end_with?("-CONTEXT.md") || f == "CONTEXT.md" } rescue nil
138
- result[:context_content] = safe_read_file(File.join(phase_dir_full, context_file)) if context_file
139
- end
93
+ def self.verify_work(phase, raw: false)
94
+ Output.error("phase required for init verify-work") unless phase
140
95
 
141
- if includes.include?("research") && phase_info&.dig(:directory)
142
- phase_dir_full = File.join(cwd, phase_info[:directory])
143
- research_file = Dir.children(phase_dir_full).find { |f| f.end_with?("-RESEARCH.md") || f == "RESEARCH.md" } rescue nil
144
- result[:research_content] = safe_read_file(File.join(phase_dir_full, research_file)) if research_file
145
- end
96
+ cwd = Dir.pwd
97
+ config = ConfigManager.load_config(cwd)
98
+ phase_info = find_phase(cwd, phase)
99
+ summaries = phase_info&.dig(:summaries) || []
146
100
 
147
- if includes.include?("verification") && phase_info&.dig(:directory)
148
- phase_dir_full = File.join(cwd, phase_info[:directory])
149
- verification_file = Dir.children(phase_dir_full).find { |f| f.end_with?("-VERIFICATION.md") || f == "VERIFICATION.md" } rescue nil
150
- result[:verification_content] = safe_read_file(File.join(phase_dir_full, verification_file)) if verification_file
101
+ summary_paths = summaries.map do |s|
102
+ File.join(phase_info[:directory], s)
151
103
  end
152
104
 
153
- if includes.include?("uat") && phase_info&.dig(:directory)
154
- phase_dir_full = File.join(cwd, phase_info[:directory])
155
- uat_file = Dir.children(phase_dir_full).find { |f| f.end_with?("-UAT.md") || f == "UAT.md" } rescue nil
156
- result[:uat_content] = safe_read_file(File.join(phase_dir_full, uat_file)) if uat_file
157
- end
105
+ result = base_result(config).merge(
106
+ verifier_model: resolve_model(cwd, "ariadna-verifier"),
107
+ phase_found: !phase_info.nil?,
108
+ phase_dir: phase_info&.dig(:directory),
109
+ phase_number: phase_info&.dig(:phase_number),
110
+ phase_name: phase_info&.dig(:phase_name),
111
+ has_verification: phase_info&.dig(:has_verification) || false,
112
+ summary_paths: summary_paths
113
+ )
158
114
 
159
115
  Output.json(result, raw: raw)
160
116
  end
@@ -163,33 +119,13 @@ module Ariadna
163
119
  cwd = Dir.pwd
164
120
  config = ConfigManager.load_config(cwd)
165
121
 
166
- # Detect existing code
167
- has_code = false
168
- begin
169
- files = `find #{cwd} -maxdepth 3 \\( -name "*.ts" -o -name "*.js" -o -name "*.py" -o -name "*.go" -o -name "*.rs" -o -name "*.swift" -o -name "*.java" -o -name "*.rb" \\) 2>/dev/null | grep -v node_modules | grep -v .git | head -5`
170
- has_code = !files.strip.empty?
171
- rescue StandardError
172
- # ignore
173
- end
174
-
175
- has_package_file = %w[package.json requirements.txt Cargo.toml go.mod Package.swift Gemfile].any? do |f|
176
- File.exist?(File.join(cwd, f))
177
- end
178
-
179
- result = {
122
+ result = base_result(config).merge(
180
123
  researcher_model: resolve_model(cwd, "ariadna-project-researcher"),
181
- synthesizer_model: resolve_model(cwd, "ariadna-research-synthesizer"),
182
124
  roadmapper_model: resolve_model(cwd, "ariadna-roadmapper"),
183
- commit_docs: config["commit_docs"],
184
- project_exists: path_exists?(cwd, ".ariadna_planning/PROJECT.md"),
185
- has_codebase_map: path_exists?(cwd, ".ariadna_planning/codebase"),
186
- planning_exists: path_exists?(cwd, ".ariadna_planning"),
187
- has_existing_code: has_code,
188
- has_package_file: has_package_file,
189
- is_brownfield: has_code || has_package_file,
190
- needs_codebase_map: (has_code || has_package_file) && !path_exists?(cwd, ".ariadna_planning/codebase"),
191
- has_git: path_exists?(cwd, ".git")
192
- }
125
+ is_brownfield: detect_brownfield(cwd),
126
+ has_git: File.exist?(File.join(cwd, ".git")),
127
+ planning_exists: File.directory?(File.join(cwd, ".ariadna_planning"))
128
+ )
193
129
 
194
130
  Output.json(result, raw: raw)
195
131
  end
@@ -197,20 +133,11 @@ module Ariadna
197
133
  def self.new_milestone(raw: false)
198
134
  cwd = Dir.pwd
199
135
  config = ConfigManager.load_config(cwd)
200
- milestone = get_milestone_info(cwd)
201
136
 
202
- result = {
203
- researcher_model: resolve_model(cwd, "ariadna-project-researcher"),
204
- synthesizer_model: resolve_model(cwd, "ariadna-research-synthesizer"),
137
+ result = base_result(config).merge(
205
138
  roadmapper_model: resolve_model(cwd, "ariadna-roadmapper"),
206
- commit_docs: config["commit_docs"],
207
- research_enabled: config["research"],
208
- current_milestone: milestone[:version],
209
- current_milestone_name: milestone[:name],
210
- project_exists: path_exists?(cwd, ".ariadna_planning/PROJECT.md"),
211
- roadmap_exists: path_exists?(cwd, ".ariadna_planning/ROADMAP.md"),
212
- state_exists: path_exists?(cwd, ".ariadna_planning/STATE.md")
213
- }
139
+ current_milestone: milestone_version(cwd)
140
+ )
214
141
 
215
142
  Output.json(result, raw: raw)
216
143
  end
@@ -218,31 +145,18 @@ module Ariadna
218
145
  def self.quick(description, raw: false)
219
146
  cwd = Dir.pwd
220
147
  config = ConfigManager.load_config(cwd)
221
- now = Time.now.utc
222
- slug = description && !description.empty? ? generate_slug(description)&.slice(0, 40) : nil
223
-
224
- quick_dir = File.join(cwd, ".ariadna_planning", "quick")
225
- next_num = 1
226
- if File.directory?(quick_dir)
227
- existing = Dir.children(quick_dir)
228
- .filter_map { |f| m = f.match(/\A(\d+)-/); m ? m[1].to_i : nil }
229
- next_num = existing.max + 1 if existing.any?
230
- end
148
+ slug = generate_slug(description)&.slice(0, 40) if description && !description.empty?
149
+ next_num = next_quick_number(cwd)
231
150
 
232
- result = {
151
+ result = base_result(config).merge(
233
152
  planner_model: resolve_model(cwd, "ariadna-planner"),
234
153
  executor_model: resolve_model(cwd, "ariadna-executor"),
235
- commit_docs: config["commit_docs"],
236
154
  next_num: next_num,
237
155
  slug: slug,
238
156
  description: description && !description.empty? ? description : nil,
239
- date: now.strftime("%Y-%m-%d"),
240
- timestamp: now.iso8601,
241
157
  quick_dir: ".ariadna_planning/quick",
242
- task_dir: slug ? ".ariadna_planning/quick/#{next_num}-#{slug}" : nil,
243
- roadmap_exists: path_exists?(cwd, ".ariadna_planning/ROADMAP.md"),
244
- planning_exists: path_exists?(cwd, ".ariadna_planning")
245
- }
158
+ task_dir: slug ? ".ariadna_planning/quick/#{next_num}-#{slug}" : nil
159
+ )
246
160
 
247
161
  Output.json(result, raw: raw)
248
162
  end
@@ -251,65 +165,34 @@ module Ariadna
251
165
  cwd = Dir.pwd
252
166
  config = ConfigManager.load_config(cwd)
253
167
 
254
- interrupted_agent_id = nil
255
168
  agent_file = File.join(cwd, ".ariadna_planning", "current-agent-id.txt")
256
- interrupted_agent_id = File.read(agent_file).strip if File.exist?(agent_file)
257
-
258
- result = {
259
- state_exists: path_exists?(cwd, ".ariadna_planning/STATE.md"),
260
- roadmap_exists: path_exists?(cwd, ".ariadna_planning/ROADMAP.md"),
261
- project_exists: path_exists?(cwd, ".ariadna_planning/PROJECT.md"),
262
- planning_exists: path_exists?(cwd, ".ariadna_planning"),
263
- has_interrupted_agent: !interrupted_agent_id.nil?,
264
- interrupted_agent_id: interrupted_agent_id,
265
- commit_docs: config["commit_docs"]
266
- }
267
-
268
- Output.json(result, raw: raw)
269
- end
270
-
271
- def self.verify_work(phase, raw: false)
272
- Output.error("phase required for init verify-work") unless phase
169
+ agent_id = File.exist?(agent_file) ? File.read(agent_file).strip : nil
273
170
 
274
- cwd = Dir.pwd
275
- config = ConfigManager.load_config(cwd)
276
- phase_info = find_phase_internal(cwd, phase)
277
-
278
- result = {
279
- planner_model: resolve_model(cwd, "ariadna-planner"),
280
- checker_model: resolve_model(cwd, "ariadna-plan-checker"),
281
- commit_docs: config["commit_docs"],
282
- phase_found: !phase_info.nil?,
283
- phase_dir: phase_info&.dig(:directory),
284
- phase_number: phase_info&.dig(:phase_number),
285
- phase_name: phase_info&.dig(:phase_name),
286
- has_verification: phase_info&.dig(:has_verification) || false
287
- }
171
+ result = base_result(config).merge(
172
+ has_interrupted_agent: !agent_id.nil?,
173
+ interrupted_agent_id: agent_id,
174
+ planning_exists: File.directory?(File.join(cwd, ".ariadna_planning"))
175
+ )
288
176
 
289
177
  Output.json(result, raw: raw)
290
178
  end
291
179
 
292
- def self.phase_op(phase, raw: false)
180
+ def self.phase_op(phase, raw: false) # rubocop:disable Metrics/CyclomaticComplexity
293
181
  cwd = Dir.pwd
294
182
  config = ConfigManager.load_config(cwd)
295
- phase_info = find_phase_internal(cwd, phase)
183
+ phase_info = find_phase(cwd, phase)
296
184
 
297
- result = {
298
- commit_docs: config["commit_docs"],
185
+ result = base_result(config).merge(
299
186
  phase_found: !phase_info.nil?,
300
187
  phase_dir: phase_info&.dig(:directory),
301
188
  phase_number: phase_info&.dig(:phase_number),
302
189
  phase_name: phase_info&.dig(:phase_name),
303
- phase_slug: phase_info&.dig(:phase_slug),
304
190
  padded_phase: phase_info&.dig(:phase_number)&.rjust(2, "0"),
305
191
  has_research: phase_info&.dig(:has_research) || false,
306
192
  has_context: phase_info&.dig(:has_context) || false,
307
- has_plans: (phase_info&.dig(:plans)&.length || 0) > 0,
308
193
  has_verification: phase_info&.dig(:has_verification) || false,
309
- plan_count: phase_info&.dig(:plans)&.length || 0,
310
- roadmap_exists: path_exists?(cwd, ".ariadna_planning/ROADMAP.md"),
311
- planning_exists: path_exists?(cwd, ".ariadna_planning")
312
- }
194
+ plan_count: phase_info&.dig(:plans)&.length || 0
195
+ )
313
196
 
314
197
  Output.json(result, raw: raw)
315
198
  end
@@ -317,42 +200,14 @@ module Ariadna
317
200
  def self.todos(area, raw: false)
318
201
  cwd = Dir.pwd
319
202
  config = ConfigManager.load_config(cwd)
320
- now = Time.now.utc
203
+ todo_list = collect_todos(cwd, area)
321
204
 
322
- pending_dir = File.join(cwd, ".ariadna_planning", "todos", "pending")
323
- count = 0
324
- todo_list = []
325
-
326
- if File.directory?(pending_dir)
327
- Dir[File.join(pending_dir, "*.md")].each do |file|
328
- content = File.read(file)
329
- created = content[/^created:\s*(.+)$/i, 1]&.strip || "unknown"
330
- title = content[/^title:\s*(.+)$/i, 1]&.strip || "Untitled"
331
- todo_area = content[/^area:\s*(.+)$/i, 1]&.strip || "general"
332
-
333
- next if area && todo_area != area
334
-
335
- count += 1
336
- todo_list << {
337
- file: File.basename(file), created: created, title: title, area: todo_area,
338
- path: File.join(".ariadna_planning", "todos", "pending", File.basename(file))
339
- }
340
- end
341
- end
342
-
343
- result = {
344
- commit_docs: config["commit_docs"],
345
- date: now.strftime("%Y-%m-%d"),
346
- timestamp: now.iso8601,
347
- todo_count: count,
205
+ result = base_result(config).merge(
206
+ todo_count: todo_list.length,
348
207
  todos: todo_list,
349
208
  area_filter: area,
350
- pending_dir: ".ariadna_planning/todos/pending",
351
- completed_dir: ".ariadna_planning/todos/completed",
352
- planning_exists: path_exists?(cwd, ".ariadna_planning"),
353
- todos_dir_exists: path_exists?(cwd, ".ariadna_planning/todos"),
354
- pending_dir_exists: path_exists?(cwd, ".ariadna_planning/todos/pending")
355
- }
209
+ pending_dir: ".ariadna_planning/todos/pending"
210
+ )
356
211
 
357
212
  Output.json(result, raw: raw)
358
213
  end
@@ -360,43 +215,11 @@ module Ariadna
360
215
  def self.milestone_op(raw: false)
361
216
  cwd = Dir.pwd
362
217
  config = ConfigManager.load_config(cwd)
363
- milestone = get_milestone_info(cwd)
364
218
 
365
- phases_dir = File.join(cwd, ".ariadna_planning", "phases")
366
- phase_count = 0
367
- completed_phases = 0
368
-
369
- if File.directory?(phases_dir)
370
- dirs = Dir.children(phases_dir).select { |d| File.directory?(File.join(phases_dir, d)) }
371
- phase_count = dirs.length
372
- dirs.each do |dir|
373
- phase_files = Dir.children(File.join(phases_dir, dir))
374
- completed_phases += 1 if phase_files.any? { |f| f.end_with?("-SUMMARY.md") || f == "SUMMARY.md" }
375
- end
376
- end
377
-
378
- archive_dir = File.join(cwd, ".ariadna_planning", "archive")
379
- archived_milestones = []
380
- if File.directory?(archive_dir)
381
- archived_milestones = Dir.children(archive_dir).select { |e| File.directory?(File.join(archive_dir, e)) }
382
- end
383
-
384
- result = {
385
- commit_docs: config["commit_docs"],
386
- milestone_version: milestone[:version],
387
- milestone_name: milestone[:name],
388
- milestone_slug: generate_slug(milestone[:name]),
389
- phase_count: phase_count,
390
- completed_phases: completed_phases,
391
- all_phases_complete: phase_count > 0 && phase_count == completed_phases,
392
- archived_milestones: archived_milestones,
393
- archive_count: archived_milestones.length,
394
- project_exists: path_exists?(cwd, ".ariadna_planning/PROJECT.md"),
395
- roadmap_exists: path_exists?(cwd, ".ariadna_planning/ROADMAP.md"),
396
- state_exists: path_exists?(cwd, ".ariadna_planning/STATE.md"),
397
- archive_exists: path_exists?(cwd, ".ariadna_planning/archive"),
398
- phases_dir_exists: path_exists?(cwd, ".ariadna_planning/phases")
399
- }
219
+ result = base_result(config).merge(
220
+ milestone_version: milestone_version(cwd),
221
+ planning_exists: File.directory?(File.join(cwd, ".ariadna_planning"))
222
+ )
400
223
 
401
224
  Output.json(result, raw: raw)
402
225
  end
@@ -405,170 +228,89 @@ module Ariadna
405
228
  cwd = Dir.pwd
406
229
  config = ConfigManager.load_config(cwd)
407
230
 
408
- codebase_dir = File.join(cwd, ".ariadna_planning", "codebase")
409
- existing_maps = []
410
- existing_maps = Dir.children(codebase_dir).select { |f| f.end_with?(".md") } if File.directory?(codebase_dir)
411
-
412
- result = {
231
+ result = base_result(config).merge(
413
232
  mapper_model: resolve_model(cwd, "ariadna-codebase-mapper"),
414
- commit_docs: config["commit_docs"],
415
- search_gitignored: config["search_gitignored"],
416
- parallelization: config["parallelization"],
417
- codebase_dir: ".ariadna_planning/codebase",
418
- existing_maps: existing_maps,
419
- has_maps: existing_maps.any?,
420
- planning_exists: path_exists?(cwd, ".ariadna_planning"),
421
- codebase_dir_exists: path_exists?(cwd, ".ariadna_planning/codebase")
422
- }
233
+ codebase_dir: ".ariadna_planning/codebase"
234
+ )
423
235
 
424
236
  Output.json(result, raw: raw)
425
237
  end
426
238
 
427
- def self.init_progress(includes, raw: false)
239
+ def self.init_progress(raw: false)
428
240
  cwd = Dir.pwd
429
241
  config = ConfigManager.load_config(cwd)
430
- milestone = get_milestone_info(cwd)
431
-
432
- phases_dir = File.join(cwd, ".ariadna_planning", "phases")
433
- phases = []
434
- current_phase = nil
435
- next_phase = nil
436
-
437
- if File.directory?(phases_dir)
438
- dirs = Dir.children(phases_dir)
439
- .select { |d| File.directory?(File.join(phases_dir, d)) }
440
- .sort
441
-
442
- dirs.each do |dir|
443
- m = dir.match(/\A(\d+(?:\.\d+)?)-?(.*)/)
444
- phase_number = m ? m[1] : dir
445
- phase_name = m && !m[2].empty? ? m[2] : nil
446
-
447
- phase_path = File.join(phases_dir, dir)
448
- phase_files = Dir.children(phase_path)
449
-
450
- plans = phase_files.select { |f| f.end_with?("-PLAN.md") || f == "PLAN.md" }
451
- summaries = phase_files.select { |f| f.end_with?("-SUMMARY.md") || f == "SUMMARY.md" }
452
- has_research = phase_files.any? { |f| f.end_with?("-RESEARCH.md") || f == "RESEARCH.md" }
453
-
454
- status = if summaries.length >= plans.length && plans.any?
455
- "complete"
456
- elsif plans.any?
457
- "in_progress"
458
- elsif has_research
459
- "researched"
460
- else
461
- "pending"
462
- end
463
-
464
- phase_entry = {
465
- number: phase_number, name: phase_name,
466
- directory: File.join(".ariadna_planning", "phases", dir),
467
- status: status, plan_count: plans.length,
468
- summary_count: summaries.length, has_research: has_research
469
- }
470
-
471
- phases << phase_entry
472
- current_phase ||= phase_entry if status == "in_progress" || status == "researched"
473
- next_phase ||= phase_entry if status == "pending"
474
- end
475
- end
476
-
477
- paused_at = nil
478
- state_path = File.join(cwd, ".ariadna_planning", "STATE.md")
479
- if File.exist?(state_path)
480
- state_content = File.read(state_path)
481
- pause_match = state_content.match(/\*\*Paused At:\*\*\s*(.+)/)
482
- paused_at = pause_match[1].strip if pause_match
483
- end
242
+ phases = scan_phases(cwd)
484
243
 
485
- result = {
486
- executor_model: resolve_model(cwd, "ariadna-executor"),
487
- planner_model: resolve_model(cwd, "ariadna-planner"),
488
- commit_docs: config["commit_docs"],
489
- milestone_version: milestone[:version],
490
- milestone_name: milestone[:name],
244
+ result = base_result(config).merge(
245
+ milestone_version: milestone_version(cwd),
491
246
  phases: phases,
492
247
  phase_count: phases.length,
493
248
  completed_count: phases.count { |p| p[:status] == "complete" },
494
249
  in_progress_count: phases.count { |p| p[:status] == "in_progress" },
495
- current_phase: current_phase,
496
- next_phase: next_phase,
497
- paused_at: paused_at,
498
- has_work_in_progress: !current_phase.nil?,
499
- project_exists: path_exists?(cwd, ".ariadna_planning/PROJECT.md"),
500
- roadmap_exists: path_exists?(cwd, ".ariadna_planning/ROADMAP.md"),
501
- state_exists: path_exists?(cwd, ".ariadna_planning/STATE.md")
502
- }
503
-
504
- result[:state_content] = safe_read_file(File.join(cwd, ".ariadna_planning", "STATE.md")) if includes.include?("state")
505
- result[:roadmap_content] = safe_read_file(File.join(cwd, ".ariadna_planning", "ROADMAP.md")) if includes.include?("roadmap")
506
- result[:project_content] = safe_read_file(File.join(cwd, ".ariadna_planning", "PROJECT.md")) if includes.include?("project")
507
- result[:config_content] = safe_read_file(File.join(cwd, ".ariadna_planning", "config.json")) if includes.include?("config")
250
+ has_work_in_progress: phases.any? { |p| p[:status] == "in_progress" }
251
+ )
508
252
 
509
253
  Output.json(result, raw: raw)
510
254
  end
511
255
 
512
256
  # --- Private helpers ---
513
257
 
514
- def self.find_phase_internal(cwd, phase)
258
+ def self.base_result(config)
259
+ { memory_dir: StateManager::MEMORY_DIR, config: config }
260
+ end
261
+
262
+ def self.find_phase(cwd, phase)
515
263
  return nil unless phase
516
264
 
517
265
  phases_dir = File.join(cwd, ".ariadna_planning", "phases")
518
266
  normalized = normalize_phase(phase)
519
-
520
267
  return nil unless File.directory?(phases_dir)
521
268
 
522
269
  dirs = Dir.children(phases_dir).select { |d| File.directory?(File.join(phases_dir, d)) }.sort
523
270
  match = dirs.find { |d| d.start_with?(normalized) }
524
271
  return nil unless match
525
272
 
273
+ parse_phase_dir(phases_dir, match, normalized)
274
+ rescue StandardError
275
+ nil
276
+ end
277
+
278
+ def self.parse_phase_dir(phases_dir, match, _normalized) # rubocop:disable Metrics
526
279
  dir_match = match.match(/\A(\d+(?:\.\d+)?)-?(.*)/)
527
- phase_number = dir_match ? dir_match[1] : normalized
280
+ phase_number = dir_match ? dir_match[1] : match
528
281
  phase_name = dir_match && !dir_match[2].empty? ? dir_match[2] : nil
529
- phase_slug = phase_name ? phase_name.downcase.gsub(/[^a-z0-9]+/, "-").gsub(/\A-+|-+\z/, "") : nil
530
282
 
531
283
  phase_dir = File.join(phases_dir, match)
532
- phase_files = Dir.children(phase_dir)
533
-
534
- plans = phase_files.select { |f| f.end_with?("-PLAN.md") || f == "PLAN.md" }.sort
535
- summaries = phase_files.select { |f| f.end_with?("-SUMMARY.md") || f == "SUMMARY.md" }.sort
536
- has_research = phase_files.any? { |f| f.end_with?("-RESEARCH.md") || f == "RESEARCH.md" }
537
- has_context = phase_files.any? { |f| f.end_with?("-CONTEXT.md") || f == "CONTEXT.md" }
538
- has_verification = phase_files.any? { |f| f.end_with?("-VERIFICATION.md") || f == "VERIFICATION.md" }
539
-
540
- completed_plan_ids = summaries.map { |s| s.sub(/-SUMMARY\.md$/, "").sub(/\ASUMMARY\.md$/, "") }
541
- incomplete_plans = plans.reject do |p|
542
- plan_id = p.sub(/-PLAN\.md$/, "").sub(/\APLAN\.md$/, "")
543
- completed_plan_ids.include?(plan_id)
544
- end
284
+ files = Dir.children(phase_dir)
545
285
 
546
286
  {
547
287
  directory: File.join(".ariadna_planning", "phases", match),
548
288
  phase_number: phase_number,
549
289
  phase_name: phase_name,
550
- phase_slug: phase_slug,
551
- plans: plans,
552
- summaries: summaries,
553
- incomplete_plans: incomplete_plans,
554
- has_research: has_research,
555
- has_context: has_context,
556
- has_verification: has_verification
290
+ phase_slug: generate_slug(phase_name),
291
+ plans: files.select { |f| f.end_with?("-PLAN.md") || f == "PLAN.md" }.sort,
292
+ summaries: files.select { |f| f.end_with?("-SUMMARY.md") || f == "SUMMARY.md" }.sort,
293
+ has_research: files.any? { |f| f.end_with?("-RESEARCH.md") || f == "RESEARCH.md" },
294
+ has_context: files.any? { |f| f.end_with?("-CONTEXT.md") || f == "CONTEXT.md" },
295
+ has_verification: files.any? { |f| f.end_with?("-VERIFICATION.md") || f == "VERIFICATION.md" }
557
296
  }
558
- rescue StandardError
559
- nil
560
297
  end
561
298
 
562
- def self.get_milestone_info(cwd)
563
- roadmap = File.read(File.join(cwd, ".ariadna_planning", "ROADMAP.md"))
564
- version_match = roadmap.match(/v(\d+\.\d+)/)
565
- name_match = roadmap.match(/## .*v\d+\.\d+[:\s]+([^\n(]+)/)
566
- {
567
- version: version_match ? version_match[0] : "v1.0",
568
- name: name_match ? name_match[1].strip : "milestone"
569
- }
570
- rescue Errno::ENOENT
571
- { version: "v1.0", name: "milestone" }
299
+ def self.build_plan_metadata(cwd, phase_info)
300
+ return [] unless phase_info
301
+
302
+ phase_dir = File.join(cwd, phase_info[:directory])
303
+ phase_info[:plans].map do |plan_file|
304
+ fm = Frontmatter.extract(File.read(File.join(phase_dir, plan_file)))
305
+ { file: plan_file, domain: fm["domain"] || "general", wave: fm["wave"] || "1" }
306
+ end
307
+ end
308
+
309
+ def self.incomplete_plan_files(_cwd, phase_info)
310
+ return [] unless phase_info
311
+
312
+ completed_ids = (phase_info[:summaries] || []).map { |s| s.sub(/-SUMMARY\.md$/, "") }
313
+ (phase_info[:plans] || []).reject { |p| completed_ids.include?(p.sub(/-PLAN\.md$/, "")) }
572
314
  end
573
315
 
574
316
  def self.resolve_model(cwd, agent_type)
@@ -577,14 +319,21 @@ module Ariadna
577
319
  ModelProfiles.resolve_model(agent_type, profile)
578
320
  end
579
321
 
580
- def self.path_exists?(cwd, relative_path)
581
- File.exist?(File.join(cwd, relative_path))
322
+ def self.milestone_version(cwd)
323
+ roadmap = File.read(File.join(cwd, ".ariadna_planning", "ROADMAP.md"))
324
+ match = roadmap.match(/v(\d+\.\d+)/)
325
+ match ? match[0] : "v1.0"
326
+ rescue Errno::ENOENT
327
+ "v1.0"
582
328
  end
583
329
 
584
- def self.safe_read_file(path)
585
- File.read(path)
586
- rescue StandardError
587
- nil
330
+ def self.normalize_phase(phase)
331
+ match = phase.to_s.match(/\A(\d+(?:\.\d+)?)/)
332
+ return phase.to_s unless match
333
+
334
+ parts = match[1].split(".")
335
+ padded = parts[0].rjust(2, "0")
336
+ parts.length > 1 ? "#{padded}.#{parts[1]}" : padded
588
337
  end
589
338
 
590
339
  def self.generate_slug(text)
@@ -593,28 +342,79 @@ module Ariadna
593
342
  text.downcase.gsub(/[^a-z0-9]+/, "-").gsub(/\A-+|-+\z/, "")
594
343
  end
595
344
 
596
- def self.normalize_phase(phase)
597
- match = phase.to_s.match(/\A(\d+(?:\.\d+)?)/)
598
- return phase.to_s unless match
345
+ def self.detect_brownfield(cwd)
346
+ package_files = %w[package.json requirements.txt Cargo.toml go.mod Gemfile]
347
+ return true if package_files.any? { |f| File.exist?(File.join(cwd, f)) }
599
348
 
600
- parts = match[1].split(".")
601
- padded = parts[0].rjust(2, "0")
602
- parts.length > 1 ? "#{padded}.#{parts[1]}" : padded
349
+ exts = %w[*.ts *.js *.py *.rb].map { |e| "-name \"#{e}\"" }.join(" -o ")
350
+ cmd = "find #{cwd} -maxdepth 3 \\( #{exts} \\) 2>/dev/null"
351
+ !`#{cmd} | grep -v node_modules | grep -v .git | head -3`.strip.empty?
352
+ rescue StandardError
353
+ false
603
354
  end
604
355
 
605
- def self.parse_include_flag(argv)
606
- idx = argv.index("--include")
607
- return [] unless idx
356
+ def self.next_quick_number(cwd)
357
+ quick_dir = File.join(cwd, ".ariadna_planning", "quick")
358
+ return 1 unless File.directory?(quick_dir)
359
+
360
+ existing = Dir.children(quick_dir).filter_map do |f|
361
+ m = f.match(/\A(\d+)-/)
362
+ m ? m[1].to_i : nil
363
+ end
364
+ existing.any? ? existing.max + 1 : 1
365
+ end
608
366
 
609
- value = argv[idx + 1]
610
- return [] unless value
367
+ def self.collect_todos(cwd, area) # rubocop:disable Metrics
368
+ pending_dir = File.join(cwd, ".ariadna_planning", "todos", "pending")
369
+ return [] unless File.directory?(pending_dir)
370
+
371
+ Dir[File.join(pending_dir, "*.md")].filter_map do |file|
372
+ content = File.read(file)
373
+ todo_area = content[/^area:\s*(.+)$/i, 1]&.strip || "general"
374
+ next if area && todo_area != area
375
+
376
+ {
377
+ file: File.basename(file),
378
+ title: content[/^title:\s*(.+)$/i, 1]&.strip || "Untitled",
379
+ area: todo_area,
380
+ path: File.join(".ariadna_planning", "todos", "pending", File.basename(file))
381
+ }
382
+ end
383
+ end
384
+
385
+ def self.scan_phases(cwd)
386
+ phases_dir = File.join(cwd, ".ariadna_planning", "phases")
387
+ return [] unless File.directory?(phases_dir)
388
+
389
+ Dir.children(phases_dir)
390
+ .select { |d| File.directory?(File.join(phases_dir, d)) }
391
+ .sort
392
+ .map { |dir| build_phase_entry(phases_dir, dir) }
393
+ end
611
394
 
612
- value.split(",").map(&:strip)
395
+ def self.build_phase_entry(phases_dir, dir) # rubocop:disable Metrics
396
+ m = dir.match(/\A(\d+(?:\.\d+)?)-?(.*)/)
397
+ phase_number = m ? m[1] : dir
398
+ files = Dir.children(File.join(phases_dir, dir))
399
+ plans = files.select { |f| f.end_with?("-PLAN.md") || f == "PLAN.md" }
400
+ summaries = files.select { |f| f.end_with?("-SUMMARY.md") || f == "SUMMARY.md" }
401
+
402
+ status = if summaries.length >= plans.length && plans.any?
403
+ "complete"
404
+ elsif plans.any?
405
+ "in_progress"
406
+ else
407
+ "pending"
408
+ end
409
+
410
+ { number: phase_number, directory: File.join(".ariadna_planning", "phases", dir), status: status }
613
411
  end
614
412
 
615
- private_class_method :find_phase_internal, :get_milestone_info, :resolve_model,
616
- :path_exists?, :safe_read_file, :generate_slug,
617
- :normalize_phase, :parse_include_flag
413
+ private_class_method :base_result, :find_phase, :parse_phase_dir,
414
+ :build_plan_metadata, :incomplete_plan_files,
415
+ :resolve_model, :milestone_version, :normalize_phase,
416
+ :generate_slug, :detect_brownfield, :next_quick_number,
417
+ :collect_todos, :scan_phases, :build_phase_entry
618
418
  end
619
419
  end
620
420
  end