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
@@ -51,22 +51,14 @@ module Ariadna
51
51
  end
52
52
  file_count = file_mentions.length
53
53
 
54
- template = "templates/summary-standard.md"
55
- type = "standard"
56
-
57
- if task_count <= 2 && file_count <= 3 && !has_decisions
58
- template = "templates/summary-minimal.md"
59
- type = "minimal"
60
- elsif has_decisions || file_count > 6 || task_count > 5
61
- template = "templates/summary-complex.md"
62
- type = "complex"
63
- end
54
+ template = "templates/summary.md"
55
+ type = "summary"
64
56
 
65
57
  Output.json({ template: template, type: type, taskCount: task_count, fileCount: file_count, hasDecisions: has_decisions },
66
58
  raw: raw, raw_value: template)
67
59
  rescue StandardError => e
68
- Output.json({ template: "templates/summary-standard.md", type: "standard", error: e.message },
69
- raw: raw, raw_value: "templates/summary-standard.md")
60
+ Output.json({ template: "templates/summary.md", type: "summary", error: e.message },
61
+ raw: raw, raw_value: "templates/summary.md")
70
62
  end
71
63
  end
72
64
 
@@ -8,177 +8,36 @@ module Ariadna
8
8
  def self.dispatch(argv, raw: false)
9
9
  subcommand = argv.shift
10
10
  case subcommand
11
- when "plan-structure"
12
- verify_plan_structure(argv.first, raw: raw)
13
- when "phase-completeness"
14
- verify_phase_completeness(argv.first, raw: raw)
15
- when "references"
16
- verify_references(argv.first, raw: raw)
17
11
  when "commits"
18
12
  verify_commits(argv, raw: raw)
13
+ when "phase-completeness"
14
+ verify_phase_completeness(argv.first, raw: raw)
19
15
  when "artifacts"
20
16
  verify_artifacts(argv.first, raw: raw)
21
- when "key-links"
22
- verify_key_links(argv.first, raw: raw)
23
17
  else
24
- Output.error("Unknown verify subcommand. Available: plan-structure, phase-completeness, references, commits, artifacts, key-links")
18
+ Output.error("Unknown verify subcommand. Available: commits, phase-completeness, artifacts")
25
19
  end
26
20
  end
27
21
 
28
- def self.validate_dispatch(argv, raw: false)
29
- subcommand = argv.shift
30
- case subcommand
31
- when "consistency"
32
- validate_consistency(raw: raw)
33
- else
34
- Output.error("Unknown validate subcommand. Available: consistency")
35
- end
36
- end
37
-
38
- def self.verify_summary(argv, raw: false)
39
- summary_path = argv.shift
40
- Output.error("summary-path required") unless summary_path
41
-
42
- count_idx = argv.index("--check-count")
43
- check_count = count_idx ? argv[count_idx + 1].to_i : 2
22
+ def self.verify_commits(argv, raw: false)
23
+ Output.error("At least one commit hash required") if argv.empty?
44
24
 
45
25
  cwd = Dir.pwd
46
- full_path = File.join(cwd, summary_path)
47
-
48
- unless File.exist?(full_path)
49
- result = {
50
- passed: false,
51
- checks: {
52
- summary_exists: false,
53
- files_created: { checked: 0, found: 0, missing: [] },
54
- commits_exist: false,
55
- self_check: "not_found"
56
- },
57
- errors: ["SUMMARY.md not found"]
58
- }
59
- Output.json(result, raw: raw, raw_value: "failed")
60
- return
61
- end
62
-
63
- content = File.read(full_path)
64
- errors = []
65
-
66
- # Spot-check files mentioned in summary
67
- mentioned_files = []
68
- patterns = [
69
- /`([^`]+\.[a-zA-Z]+)`/,
70
- /(?:Created|Modified|Added|Updated|Edited):\s*`?([^\s`]+\.[a-zA-Z]+)`?/i
71
- ]
72
-
73
- patterns.each do |pattern|
74
- content.scan(pattern) do |match|
75
- file_path = match[0]
76
- if file_path && !file_path.start_with?("http") && file_path.include?("/")
77
- mentioned_files << file_path unless mentioned_files.include?(file_path)
78
- end
79
- end
80
- end
81
-
82
- files_to_check = mentioned_files.first(check_count)
83
- missing = files_to_check.reject { |f| File.exist?(File.join(cwd, f)) }
84
-
85
- # Check commits exist
86
- hashes = content.scan(/\b[0-9a-f]{7,40}\b/)
87
- commits_exist = false
88
- if hashes.any?
89
- hashes.first(3).each do |hash|
90
- result = exec_git(cwd, ["cat-file", "-t", hash])
91
- if result[:exit_code] == 0 && result[:stdout].strip == "commit"
92
- commits_exist = true
93
- break
94
- end
95
- end
96
- end
26
+ valid = []
27
+ invalid = []
97
28
 
98
- # Self-check section
99
- self_check = "not_found"
100
- if content.match?(/##\s*(?:Self[- ]?Check|Verification|Quality Check)/i)
101
- check_section = content[content.index(/##\s*(?:Self[- ]?Check|Verification|Quality Check)/i)..]
102
- if check_section.match?(/(?:fail|✗|❌|incomplete|blocked)/i)
103
- self_check = "failed"
104
- elsif check_section.match?(/(?:all\s+)?(?:pass|✓|✅|complete|succeeded)/i)
105
- self_check = "passed"
29
+ argv.each do |hash|
30
+ result = exec_git(cwd, ["cat-file", "-t", hash])
31
+ if result[:exit_code] == 0 && result[:stdout].strip == "commit"
32
+ valid << hash
33
+ else
34
+ invalid << hash
106
35
  end
107
36
  end
108
37
 
109
- errors << "Missing files: #{missing.join(', ')}" if missing.any?
110
- errors << "Referenced commit hashes not found in git history" if !commits_exist && hashes.any?
111
- errors << "Self-check section indicates failure" if self_check == "failed"
112
-
113
- checks = {
114
- summary_exists: true,
115
- files_created: { checked: files_to_check.length, found: files_to_check.length - missing.length, missing: missing },
116
- commits_exist: commits_exist,
117
- self_check: self_check
118
- }
119
-
120
- passed = missing.empty? && self_check != "failed"
121
- Output.json({ passed: passed, checks: checks, errors: errors }, raw: raw, raw_value: passed ? "passed" : "failed")
122
- end
123
-
124
- def self.verify_plan_structure(file_path, raw: false)
125
- Output.error("file path required") unless file_path
126
-
127
- cwd = Dir.pwd
128
- full_path = File.absolute_path?(file_path) ? file_path : File.join(cwd, file_path)
129
-
130
- unless File.exist?(full_path)
131
- Output.json({ error: "File not found", path: file_path }, raw: raw)
132
- return
133
- end
134
-
135
- content = File.read(full_path)
136
- fm = Frontmatter.extract(content)
137
- errors = []
138
- warnings = []
139
-
140
- required = %w[phase plan type wave depends_on files_modified autonomous must_haves]
141
- required.each do |field|
142
- errors << "Missing required frontmatter field: #{field}" unless fm.key?(field)
143
- end
144
-
145
- # Parse task elements
146
- tasks = []
147
- content.scan(/<task[^>]*>([\s\S]*?)<\/task>/) do
148
- task_content = ::Regexp.last_match(1)
149
- name_match = task_content.match(/<name>([\s\S]*?)<\/name>/)
150
- task_name = name_match ? name_match[1].strip : "unnamed"
151
- has_files = task_content.include?("<files>")
152
- has_action = task_content.include?("<action>")
153
- has_verify = task_content.include?("<verify>")
154
- has_done = task_content.include?("<done>")
155
-
156
- errors << "Task missing <name> element" unless name_match
157
- errors << "Task '#{task_name}' missing <action>" unless has_action
158
- warnings << "Task '#{task_name}' missing <verify>" unless has_verify
159
- warnings << "Task '#{task_name}' missing <done>" unless has_done
160
- warnings << "Task '#{task_name}' missing <files>" unless has_files
161
-
162
- tasks << { name: task_name, hasFiles: has_files, hasAction: has_action, hasVerify: has_verify, hasDone: has_done }
163
- end
164
-
165
- warnings << "No <task> elements found" if tasks.empty?
166
-
167
- # Wave/depends_on consistency
168
- if fm["wave"] && fm["wave"].to_i > 1 && (!fm["depends_on"] || (fm["depends_on"].is_a?(Array) && fm["depends_on"].empty?))
169
- warnings << "Wave > 1 but depends_on is empty"
170
- end
171
-
172
- # Autonomous/checkpoint consistency
173
- if content.match?(/<task\s+type=["']?checkpoint/) && fm["autonomous"] != "false" && fm["autonomous"] != false
174
- errors << "Has checkpoint tasks but autonomous is not false"
175
- end
176
-
177
38
  Output.json({
178
- valid: errors.empty?, errors: errors, warnings: warnings,
179
- task_count: tasks.length, tasks: tasks,
180
- frontmatter_fields: fm.keys
181
- }, raw: raw, raw_value: errors.empty? ? "valid" : "invalid")
39
+ all_valid: invalid.empty?, valid: valid, invalid: invalid, total: argv.length
40
+ }, raw: raw, raw_value: invalid.empty? ? "valid" : "invalid")
182
41
  end
183
42
 
184
43
  def self.verify_phase_completeness(phase, raw: false)
@@ -216,70 +75,6 @@ module Ariadna
216
75
  }, raw: raw, raw_value: errors.empty? ? "complete" : "incomplete")
217
76
  end
218
77
 
219
- def self.verify_references(file_path, raw: false)
220
- Output.error("file path required") unless file_path
221
-
222
- cwd = Dir.pwd
223
- full_path = File.absolute_path?(file_path) ? file_path : File.join(cwd, file_path)
224
-
225
- unless File.exist?(full_path)
226
- Output.json({ error: "File not found", path: file_path }, raw: raw)
227
- return
228
- end
229
-
230
- content = File.read(full_path)
231
- found = []
232
- missing = []
233
-
234
- # @-references
235
- content.scan(/@([^\s,)]+\/[^\s,)]+)/) do
236
- ref = ::Regexp.last_match(1)
237
- next if found.include?(ref) || missing.include?(ref)
238
-
239
- resolved = if ref.start_with?("~/")
240
- File.join(Dir.home, ref[2..])
241
- else
242
- File.join(cwd, ref)
243
- end
244
- File.exist?(resolved) ? found << ref : missing << ref
245
- end
246
-
247
- # Backtick file paths
248
- content.scan(/`([^`]+\/[^`]+\.[a-zA-Z]{1,10})`/) do
249
- ref = ::Regexp.last_match(1)
250
- next if ref.start_with?("http") || ref.include?("${") || ref.include?("{{")
251
- next if found.include?(ref) || missing.include?(ref)
252
-
253
- resolved = File.join(cwd, ref)
254
- File.exist?(resolved) ? found << ref : missing << ref
255
- end
256
-
257
- Output.json({
258
- valid: missing.empty?, found: found.length, missing: missing, total: found.length + missing.length
259
- }, raw: raw, raw_value: missing.empty? ? "valid" : "invalid")
260
- end
261
-
262
- def self.verify_commits(argv, raw: false)
263
- Output.error("At least one commit hash required") if argv.empty?
264
-
265
- cwd = Dir.pwd
266
- valid = []
267
- invalid = []
268
-
269
- argv.each do |hash|
270
- result = exec_git(cwd, ["cat-file", "-t", hash])
271
- if result[:exit_code] == 0 && result[:stdout].strip == "commit"
272
- valid << hash
273
- else
274
- invalid << hash
275
- end
276
- end
277
-
278
- Output.json({
279
- all_valid: invalid.empty?, valid: valid, invalid: invalid, total: argv.length
280
- }, raw: raw, raw_value: invalid.empty? ? "valid" : "invalid")
281
- end
282
-
283
78
  def self.verify_artifacts(plan_file_path, raw: false)
284
79
  Output.error("plan file path required") unless plan_file_path
285
80
 
@@ -301,7 +96,6 @@ module Ariadna
301
96
 
302
97
  results = []
303
98
  artifacts.each do |artifact|
304
- next if artifact.is_a?(String)
305
99
  next unless artifact.is_a?(Hash)
306
100
 
307
101
  art_path = artifact["path"]
@@ -321,12 +115,6 @@ module Ariadna
321
115
  if artifact["contains"] && !file_content.include?(artifact["contains"])
322
116
  check[:issues] << "Missing pattern: #{artifact['contains']}"
323
117
  end
324
- if artifact["exports"]
325
- exports = artifact["exports"].is_a?(Array) ? artifact["exports"] : [artifact["exports"]]
326
- exports.each do |exp|
327
- check[:issues] << "Missing export: #{exp}" unless file_content.include?(exp)
328
- end
329
- end
330
118
  check[:passed] = check[:issues].empty?
331
119
  else
332
120
  check[:issues] << "File not found"
@@ -341,161 +129,6 @@ module Ariadna
341
129
  }, raw: raw, raw_value: passed == results.length ? "valid" : "invalid")
342
130
  end
343
131
 
344
- def self.verify_key_links(plan_file_path, raw: false)
345
- Output.error("plan file path required") unless plan_file_path
346
-
347
- cwd = Dir.pwd
348
- full_path = File.absolute_path?(plan_file_path) ? plan_file_path : File.join(cwd, plan_file_path)
349
-
350
- unless File.exist?(full_path)
351
- Output.json({ error: "File not found", path: plan_file_path }, raw: raw)
352
- return
353
- end
354
-
355
- content = File.read(full_path)
356
- key_links = parse_must_haves_block(content, "key_links")
357
-
358
- if key_links.empty?
359
- Output.json({ error: "No must_haves.key_links found in frontmatter", path: plan_file_path }, raw: raw)
360
- return
361
- end
362
-
363
- results = []
364
- key_links.each do |link|
365
- next if link.is_a?(String)
366
- next unless link.is_a?(Hash)
367
-
368
- check = { from: link["from"], to: link["to"], via: link["via"] || "", verified: false, detail: "" }
369
- source_content = safe_read_file(File.join(cwd, link["from"] || ""))
370
-
371
- if !source_content
372
- check[:detail] = "Source file not found"
373
- elsif link["pattern"]
374
- begin
375
- regex = Regexp.new(link["pattern"])
376
- if regex.match?(source_content)
377
- check[:verified] = true
378
- check[:detail] = "Pattern found in source"
379
- else
380
- target_content = safe_read_file(File.join(cwd, link["to"] || ""))
381
- if target_content && regex.match?(target_content)
382
- check[:verified] = true
383
- check[:detail] = "Pattern found in target"
384
- else
385
- check[:detail] = "Pattern \"#{link['pattern']}\" not found in source or target"
386
- end
387
- end
388
- rescue RegexpError
389
- check[:detail] = "Invalid regex pattern: #{link['pattern']}"
390
- end
391
- elsif source_content.include?(link["to"] || "")
392
- check[:verified] = true
393
- check[:detail] = "Target referenced in source"
394
- else
395
- check[:detail] = "Target not referenced in source"
396
- end
397
-
398
- results << check
399
- end
400
-
401
- verified = results.count { |r| r[:verified] }
402
- Output.json({
403
- all_verified: verified == results.length, verified: verified, total: results.length, links: results
404
- }, raw: raw, raw_value: verified == results.length ? "valid" : "invalid")
405
- end
406
-
407
- def self.validate_consistency(raw: false)
408
- cwd = Dir.pwd
409
- roadmap_path = File.join(cwd, ".ariadna_planning", "ROADMAP.md")
410
- phases_dir = File.join(cwd, ".ariadna_planning", "phases")
411
- errors = []
412
- warnings = []
413
-
414
- unless File.exist?(roadmap_path)
415
- errors << "ROADMAP.md not found"
416
- Output.json({ passed: false, errors: errors, warnings: warnings }, raw: raw, raw_value: "failed")
417
- return
418
- end
419
-
420
- roadmap_content = File.read(roadmap_path)
421
-
422
- # Extract phases from ROADMAP
423
- roadmap_phases = []
424
- roadmap_content.scan(/###\s*Phase\s+(\d+(?:\.\d+)?)\s*:/i) { roadmap_phases << ::Regexp.last_match(1) }
425
-
426
- # Get phases on disk
427
- disk_phases = []
428
- if File.directory?(phases_dir)
429
- Dir.children(phases_dir).each do |dir|
430
- next unless File.directory?(File.join(phases_dir, dir))
431
-
432
- dm = dir.match(/\A(\d+(?:\.\d+)?)/)
433
- disk_phases << dm[1] if dm
434
- end
435
- end
436
-
437
- # Cross-check
438
- roadmap_phases.each do |p|
439
- normalized = normalize_phase(p)
440
- unless disk_phases.include?(p) || disk_phases.include?(normalized)
441
- warnings << "Phase #{p} in ROADMAP.md but no directory on disk"
442
- end
443
- end
444
-
445
- disk_phases.each do |p|
446
- unpadded = p.to_i.to_s
447
- unless roadmap_phases.include?(p) || roadmap_phases.include?(unpadded)
448
- warnings << "Phase #{p} exists on disk but not in ROADMAP.md"
449
- end
450
- end
451
-
452
- # Sequential numbering check
453
- integer_phases = disk_phases.reject { |p| p.include?(".") }.map(&:to_i).sort
454
- (1...integer_phases.length).each do |i|
455
- if integer_phases[i] != integer_phases[i - 1] + 1
456
- warnings << "Gap in phase numbering: #{integer_phases[i - 1]} \u2192 #{integer_phases[i]}"
457
- end
458
- end
459
-
460
- # Plan numbering and orphans
461
- if File.directory?(phases_dir)
462
- Dir.children(phases_dir).sort.each do |dir|
463
- dir_path = File.join(phases_dir, dir)
464
- next unless File.directory?(dir_path)
465
-
466
- phase_files = Dir.children(dir_path)
467
- plans = phase_files.select { |f| f.end_with?("-PLAN.md") }.sort
468
- summaries = phase_files.select { |f| f.end_with?("-SUMMARY.md") }
469
-
470
- plan_nums = plans.filter_map { |p| m = p.match(/-(\d{2})-PLAN\.md$/); m ? m[1].to_i : nil }
471
- (1...plan_nums.length).each do |i|
472
- if plan_nums[i] != plan_nums[i - 1] + 1
473
- warnings << "Gap in plan numbering in #{dir}: plan #{plan_nums[i - 1]} \u2192 #{plan_nums[i]}"
474
- end
475
- end
476
-
477
- plan_ids = plans.map { |p| p.sub(/-PLAN\.md$/, "") }
478
- summary_ids = summaries.map { |s| s.sub(/-SUMMARY\.md$/, "") }
479
- summary_ids.each do |sid|
480
- unless plan_ids.include?(sid)
481
- warnings << "Summary #{sid}-SUMMARY.md in #{dir} has no matching PLAN.md"
482
- end
483
- end
484
-
485
- # Check frontmatter wave field
486
- plans.each do |plan|
487
- content = File.read(File.join(dir_path, plan))
488
- fm = Frontmatter.extract(content)
489
- warnings << "#{dir}/#{plan}: missing 'wave' in frontmatter" unless fm["wave"]
490
- end
491
- end
492
- end
493
-
494
- passed = errors.empty?
495
- Output.json({ passed: passed, errors: errors, warnings: warnings, warning_count: warnings.length },
496
- raw: raw, raw_value: passed ? "passed" : "failed")
497
- end
498
-
499
132
  # --- Private helpers ---
500
133
 
501
134
  def self.find_phase_internal(cwd, phase)
@@ -569,29 +202,18 @@ module Ariadna
569
202
 
570
203
  if line.match?(/\A\s{6}-\s+/)
571
204
  items << current if current
572
- simple_match = line.match(/\A\s{6}-\s+"?([^"]+)"?\s*\z/)
573
- if simple_match && !line.include?(":")
574
- current = simple_match[1]
575
- else
576
- kv_match = line.match(/\A\s{6}-\s+(\w+):\s*"?([^"]*)"?\s*\z/)
577
- current = if kv_match
578
- { kv_match[1] => kv_match[2] }
579
- else
580
- {}
581
- end
582
- end
205
+ kv_match = line.match(/\A\s{6}-\s+(\w+):\s*"?([^"]*)"?\s*\z/)
206
+ current = if kv_match
207
+ { kv_match[1] => kv_match[2] }
208
+ else
209
+ {}
210
+ end
583
211
  elsif current.is_a?(Hash)
584
212
  kv_match = line.match(/\A\s{8,}(\w+):\s*"?([^"]*)"?\s*\z/)
585
213
  if kv_match
586
214
  val = kv_match[2]
587
215
  current[kv_match[1]] = val.match?(/\A\d+\z/) ? val.to_i : val
588
216
  end
589
- arr_match = line.match(/\A\s{10,}-\s+"?([^"]+)"?\s*\z/)
590
- if arr_match && current.any?
591
- last_key = current.keys.last
592
- current[last_key] = current[last_key].is_a?(Array) ? current[last_key] : (current[last_key] ? [current[last_key]] : [])
593
- current[last_key] << arr_match[1]
594
- end
595
217
  end
596
218
  end
597
219
 
@@ -13,6 +13,7 @@ module Ariadna
13
13
 
14
14
  remove_commands
15
15
  remove_agents
16
+ remove_skills
16
17
  remove_content
17
18
  remove_statusline
18
19
  remove_patches
@@ -51,6 +52,14 @@ module Ariadna
51
52
  puts " \u2713 Removed #{removed} agents" if removed > 0
52
53
  end
53
54
 
55
+ def remove_skills
56
+ dir = File.join(@target_dir, "skills")
57
+ if File.directory?(dir)
58
+ FileUtils.rm_rf(dir)
59
+ puts " \u2713 Removed skills/"
60
+ end
61
+ end
62
+
54
63
  def remove_content
55
64
  dir = File.join(@target_dir, "ariadna")
56
65
  if File.directory?(dir)
@@ -1,3 +1,3 @@
1
1
  module Ariadna
2
- VERSION = "1.3.0"
2
+ VERSION = "2.0.0"
3
3
  end
data/lib/ariadna.rb CHANGED
@@ -1,3 +1,4 @@
1
+ require "time"
1
2
  require_relative "ariadna/version"
2
3
 
3
4
  module Ariadna