aidp 0.17.1 โ†’ 0.19.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 (40) hide show
  1. checksums.yaml +4 -4
  2. data/README.md +69 -0
  3. data/lib/aidp/cli/terminal_io.rb +5 -2
  4. data/lib/aidp/cli.rb +43 -2
  5. data/lib/aidp/config.rb +9 -14
  6. data/lib/aidp/execute/agent_signal_parser.rb +20 -0
  7. data/lib/aidp/execute/persistent_tasklist.rb +220 -0
  8. data/lib/aidp/execute/prompt_manager.rb +128 -1
  9. data/lib/aidp/execute/repl_macros.rb +719 -0
  10. data/lib/aidp/execute/work_loop_runner.rb +162 -1
  11. data/lib/aidp/harness/ai_decision_engine.rb +376 -0
  12. data/lib/aidp/harness/capability_registry.rb +273 -0
  13. data/lib/aidp/harness/config_schema.rb +305 -1
  14. data/lib/aidp/harness/configuration.rb +452 -0
  15. data/lib/aidp/harness/enhanced_runner.rb +7 -1
  16. data/lib/aidp/harness/provider_factory.rb +0 -2
  17. data/lib/aidp/harness/runner.rb +7 -1
  18. data/lib/aidp/harness/thinking_depth_manager.rb +335 -0
  19. data/lib/aidp/harness/zfc_condition_detector.rb +395 -0
  20. data/lib/aidp/init/devcontainer_generator.rb +274 -0
  21. data/lib/aidp/init/runner.rb +37 -10
  22. data/lib/aidp/init.rb +1 -0
  23. data/lib/aidp/prompt_optimization/context_composer.rb +286 -0
  24. data/lib/aidp/prompt_optimization/optimizer.rb +335 -0
  25. data/lib/aidp/prompt_optimization/prompt_builder.rb +309 -0
  26. data/lib/aidp/prompt_optimization/relevance_scorer.rb +256 -0
  27. data/lib/aidp/prompt_optimization/source_code_fragmenter.rb +308 -0
  28. data/lib/aidp/prompt_optimization/style_guide_indexer.rb +240 -0
  29. data/lib/aidp/prompt_optimization/template_indexer.rb +250 -0
  30. data/lib/aidp/provider_manager.rb +0 -2
  31. data/lib/aidp/providers/anthropic.rb +19 -0
  32. data/lib/aidp/setup/wizard.rb +299 -4
  33. data/lib/aidp/utils/devcontainer_detector.rb +166 -0
  34. data/lib/aidp/version.rb +1 -1
  35. data/lib/aidp/watch/build_processor.rb +72 -6
  36. data/lib/aidp/watch/repository_client.rb +2 -1
  37. data/lib/aidp.rb +1 -1
  38. data/templates/aidp.yml.example +128 -0
  39. metadata +15 -2
  40. data/lib/aidp/providers/macos_ui.rb +0 -102
@@ -275,6 +275,30 @@ module Aidp
275
275
  usage: "/skill <list|show|use> [args]",
276
276
  example: "/skill list",
277
277
  handler: method(:cmd_skill)
278
+ },
279
+ "/tools" => {
280
+ description: "Manage available tools (coverage, testing, etc.)",
281
+ usage: "/tools <show|coverage|test> [subcommand]",
282
+ example: "/tools show",
283
+ handler: method(:cmd_tools)
284
+ },
285
+ "/thinking" => {
286
+ description: "Manage thinking depth tier for model selection",
287
+ usage: "/thinking <show|set|max|reset> [tier]",
288
+ example: "/thinking show",
289
+ handler: method(:cmd_thinking)
290
+ },
291
+ "/prompt" => {
292
+ description: "Inspect and control prompt optimization",
293
+ usage: "/prompt <explain|stats|expand|reset>",
294
+ example: "/prompt explain",
295
+ handler: method(:cmd_prompt)
296
+ },
297
+ "/tasks" => {
298
+ description: "Manage persistent tasklist (cross-session task tracking)",
299
+ usage: "/tasks <list|show|done|abandon|stats> [args]",
300
+ example: "/tasks list pending",
301
+ handler: method(:cmd_tasks)
278
302
  }
279
303
  }
280
304
  end
@@ -1489,6 +1513,701 @@ module Aidp
1489
1513
  }
1490
1514
  end
1491
1515
  end
1516
+
1517
+ # /tools command - manage available tools
1518
+ def cmd_tools(args)
1519
+ Aidp.log_debug("repl_macros", "Executing /tools command", args: args)
1520
+
1521
+ subcommand = args[0]&.downcase
1522
+
1523
+ case subcommand
1524
+ when "show"
1525
+ cmd_tools_show
1526
+ when "coverage"
1527
+ cmd_tools_coverage(args[1..])
1528
+ when "test"
1529
+ cmd_tools_test(args[1..])
1530
+ else
1531
+ {
1532
+ success: false,
1533
+ message: "Usage: /tools <command> [args]\n\nCommands:\n show - Show configured tools and their status\n coverage - Run coverage analysis and show delta\n test <type> - Run interactive tests (web, cli, desktop)\n\nExamples:\n /tools show\n /tools coverage\n /tools test web",
1534
+ action: :none
1535
+ }
1536
+ end
1537
+ end
1538
+
1539
+ def cmd_tools_show
1540
+ require_relative "../harness/configuration"
1541
+
1542
+ begin
1543
+ config = Aidp::Harness::Configuration.new(@project_dir)
1544
+
1545
+ output = ["๐Ÿ“Š Configured Tools\n", "=" * 50]
1546
+
1547
+ # Coverage tools
1548
+ if config.coverage_enabled?
1549
+ output << "\n๐Ÿ” Coverage:"
1550
+ output << " Tool: #{config.coverage_tool || "not specified"}"
1551
+ output << " Command: #{config.coverage_run_command || "not specified"}"
1552
+ output << " Report paths: #{config.coverage_report_paths.join(", ")}" if config.coverage_report_paths.any?
1553
+ output << " Fail on drop: #{config.coverage_fail_on_drop? ? "yes" : "no"}"
1554
+ output << " Minimum coverage: #{config.coverage_minimum || "not set"}%" if config.coverage_minimum
1555
+ else
1556
+ output << "\n๐Ÿ” Coverage: disabled"
1557
+ end
1558
+
1559
+ # VCS configuration
1560
+ output << "\n\n๐Ÿ—‚๏ธ Version Control:"
1561
+ output << " Tool: #{config.vcs_tool}"
1562
+ output << " Behavior: #{config.vcs_behavior}"
1563
+ output << " Conventional commits: #{config.conventional_commits? ? "yes" : "no"}"
1564
+
1565
+ # Interactive testing
1566
+ if config.interactive_testing_enabled?
1567
+ output << "\n\n๐ŸŽฏ Interactive Testing:"
1568
+ output << " App type: #{config.interactive_testing_app_type}"
1569
+ tools = config.interactive_testing_tools
1570
+ if tools.any?
1571
+ tools.each do |category, category_tools|
1572
+ output << " #{category.to_s.capitalize}:"
1573
+ category_tools.each do |tool_name, tool_config|
1574
+ next unless tool_config[:enabled]
1575
+ output << " โ€ข #{tool_name}: enabled"
1576
+ output << " Run: #{tool_config[:run]}" if tool_config[:run]
1577
+ output << " Specs: #{tool_config[:specs_dir]}" if tool_config[:specs_dir]
1578
+ end
1579
+ end
1580
+ else
1581
+ output << " No tools configured"
1582
+ end
1583
+ else
1584
+ output << "\n\n๐ŸŽฏ Interactive Testing: disabled"
1585
+ end
1586
+
1587
+ # Model families
1588
+ output << "\n\n๐Ÿค– Model Families:"
1589
+ config.configured_providers.each do |provider|
1590
+ family = config.model_family(provider)
1591
+ output << " #{provider}: #{family}"
1592
+ end
1593
+
1594
+ {
1595
+ success: true,
1596
+ message: output.join("\n"),
1597
+ action: :none
1598
+ }
1599
+ rescue => e
1600
+ Aidp.log_error("repl_macros", "Failed to show tools", error: e.message)
1601
+ {
1602
+ success: false,
1603
+ message: "Failed to load tool configuration: #{e.message}",
1604
+ action: :none
1605
+ }
1606
+ end
1607
+ end
1608
+
1609
+ def cmd_tools_coverage(args)
1610
+ require_relative "../harness/configuration"
1611
+
1612
+ begin
1613
+ config = Aidp::Harness::Configuration.new(@project_dir)
1614
+
1615
+ unless config.coverage_enabled?
1616
+ return {
1617
+ success: false,
1618
+ message: "Coverage is not enabled. Run 'aidp config --interactive' to configure coverage.",
1619
+ action: :none
1620
+ }
1621
+ end
1622
+
1623
+ unless config.coverage_run_command
1624
+ return {
1625
+ success: false,
1626
+ message: "Coverage run command not configured. Run 'aidp config --interactive' to set it up.",
1627
+ action: :none
1628
+ }
1629
+ end
1630
+
1631
+ Aidp.log_debug("repl_macros", "Running coverage", command: config.coverage_run_command)
1632
+
1633
+ {
1634
+ success: true,
1635
+ message: "Running coverage with: #{config.coverage_run_command}\n(Coverage execution to be implemented in work loop)",
1636
+ action: :run_coverage,
1637
+ data: {
1638
+ command: config.coverage_run_command,
1639
+ tool: config.coverage_tool,
1640
+ report_paths: config.coverage_report_paths
1641
+ }
1642
+ }
1643
+ rescue => e
1644
+ Aidp.log_error("repl_macros", "Failed to run coverage", error: e.message)
1645
+ {
1646
+ success: false,
1647
+ message: "Failed to run coverage: #{e.message}",
1648
+ action: :none
1649
+ }
1650
+ end
1651
+ end
1652
+
1653
+ def cmd_tools_test(args)
1654
+ require_relative "../harness/configuration"
1655
+
1656
+ test_type = args[0]&.downcase
1657
+
1658
+ begin
1659
+ config = Aidp::Harness::Configuration.new(@project_dir)
1660
+
1661
+ unless config.interactive_testing_enabled?
1662
+ return {
1663
+ success: false,
1664
+ message: "Interactive testing is not enabled. Run 'aidp config --interactive' to configure it.",
1665
+ action: :none
1666
+ }
1667
+ end
1668
+
1669
+ unless test_type
1670
+ return {
1671
+ success: false,
1672
+ message: "Usage: /tools test <type>\n\nTypes: web, cli, desktop\n\nExample: /tools test web",
1673
+ action: :none
1674
+ }
1675
+ end
1676
+
1677
+ unless %w[web cli desktop].include?(test_type)
1678
+ return {
1679
+ success: false,
1680
+ message: "Invalid test type: #{test_type}. Must be one of: web, cli, desktop",
1681
+ action: :none
1682
+ }
1683
+ end
1684
+
1685
+ tools = config.interactive_testing_tools.dig(test_type.to_sym)
1686
+ unless tools&.any? { |_, t| t[:enabled] }
1687
+ return {
1688
+ success: false,
1689
+ message: "No #{test_type} testing tools configured. Run 'aidp config --interactive' to set them up.",
1690
+ action: :none
1691
+ }
1692
+ end
1693
+
1694
+ enabled_tools = tools.select { |_, t| t[:enabled] }
1695
+ tool_list = enabled_tools.map { |name, cfg| " โ€ข #{name}: #{cfg[:run] || "no command"}" }.join("\n")
1696
+
1697
+ Aidp.log_debug("repl_macros", "Running interactive tests", type: test_type, tools: enabled_tools.keys)
1698
+
1699
+ {
1700
+ success: true,
1701
+ message: "Running #{test_type} tests:\n#{tool_list}\n(Test execution to be implemented in work loop)",
1702
+ action: :run_interactive_tests,
1703
+ data: {
1704
+ test_type: test_type,
1705
+ tools: enabled_tools
1706
+ }
1707
+ }
1708
+ rescue => e
1709
+ Aidp.log_error("repl_macros", "Failed to run interactive tests", error: e.message)
1710
+ {
1711
+ success: false,
1712
+ message: "Failed to run interactive tests: #{e.message}",
1713
+ action: :none
1714
+ }
1715
+ end
1716
+ end
1717
+
1718
+ # Command: /thinking
1719
+ def cmd_thinking(args)
1720
+ subcommand = args[0]
1721
+
1722
+ case subcommand
1723
+ when "show"
1724
+ cmd_thinking_show
1725
+ when "set"
1726
+ cmd_thinking_set(args[1])
1727
+ when "max"
1728
+ cmd_thinking_max(args[1])
1729
+ when "reset"
1730
+ cmd_thinking_reset
1731
+ else
1732
+ {
1733
+ success: false,
1734
+ message: "Unknown subcommand: #{subcommand}\nUsage: /thinking <show|set|max|reset> [tier]",
1735
+ action: :none
1736
+ }
1737
+ end
1738
+ rescue => e
1739
+ Aidp.log_error("repl_macros", "Failed to execute thinking command", error: e.message)
1740
+ {
1741
+ success: false,
1742
+ message: "Failed to execute thinking command: #{e.message}",
1743
+ action: :none
1744
+ }
1745
+ end
1746
+
1747
+ # Subcommand: /thinking show
1748
+ def cmd_thinking_show
1749
+ require_relative "../harness/configuration"
1750
+ require_relative "../harness/thinking_depth_manager"
1751
+
1752
+ config = Aidp::Harness::Configuration.new(@project_dir)
1753
+ manager = Aidp::Harness::ThinkingDepthManager.new(config, root_dir: @project_dir)
1754
+
1755
+ lines = []
1756
+ lines << "Thinking Depth Configuration:"
1757
+ lines << ""
1758
+ lines << "Current Tier: #{manager.current_tier}"
1759
+ lines << "Default Tier: #{manager.default_tier}"
1760
+ lines << "Max Tier: #{manager.max_tier}"
1761
+ lines << ""
1762
+
1763
+ # Show all available tiers
1764
+ require_relative "../harness/capability_registry"
1765
+ lines << "Available Tiers:"
1766
+ Aidp::Harness::CapabilityRegistry::VALID_TIERS.each do |tier|
1767
+ marker = if tier == manager.current_tier
1768
+ "โ†’"
1769
+ elsif tier == manager.max_tier
1770
+ "โ†‘"
1771
+ else
1772
+ " "
1773
+ end
1774
+ lines << " #{marker} #{tier}"
1775
+ end
1776
+ lines << ""
1777
+ lines << "Legend: โ†’ current, โ†‘ max allowed"
1778
+ lines << ""
1779
+
1780
+ # Show current model selection
1781
+ current_model = manager.select_model_for_tier
1782
+ if current_model
1783
+ provider, model_name, model_data = current_model
1784
+ lines << "Current Model: #{provider}/#{model_name}"
1785
+ lines << " Tier: #{model_data["tier"]}" if model_data["tier"]
1786
+ lines << " Context Window: #{model_data["context_window"]}" if model_data["context_window"]
1787
+ else
1788
+ lines << "Current Model: (none selected)"
1789
+ end
1790
+
1791
+ lines << ""
1792
+ lines << "Provider Switching: #{config.allow_provider_switch_for_tier? ? "enabled" : "disabled"}"
1793
+
1794
+ # Show escalation config
1795
+ escalation = config.escalation_config
1796
+ lines << ""
1797
+ lines << "Escalation Settings:"
1798
+ lines << " Fail Attempts Threshold: #{escalation[:on_fail_attempts]}"
1799
+ if escalation[:on_complexity_threshold]&.any?
1800
+ lines << " Complexity Thresholds:"
1801
+ escalation[:on_complexity_threshold].each do |key, value|
1802
+ lines << " #{key}: #{value}"
1803
+ end
1804
+ end
1805
+
1806
+ {
1807
+ success: true,
1808
+ message: lines.join("\n"),
1809
+ action: :display
1810
+ }
1811
+ end
1812
+
1813
+ # Subcommand: /thinking set <tier>
1814
+ def cmd_thinking_set(tier)
1815
+ unless tier
1816
+ return {
1817
+ success: false,
1818
+ message: "Usage: /thinking set <tier>\nTiers: mini, standard, thinking, pro, max",
1819
+ action: :none
1820
+ }
1821
+ end
1822
+
1823
+ require_relative "../harness/configuration"
1824
+ require_relative "../harness/thinking_depth_manager"
1825
+
1826
+ config = Aidp::Harness::Configuration.new(@project_dir)
1827
+ manager = Aidp::Harness::ThinkingDepthManager.new(config, root_dir: @project_dir)
1828
+
1829
+ old_tier = manager.current_tier
1830
+ manager.current_tier = tier
1831
+
1832
+ {
1833
+ success: true,
1834
+ message: "Thinking tier changed: #{old_tier} โ†’ #{tier}\nMax tier: #{manager.max_tier}",
1835
+ action: :tier_changed
1836
+ }
1837
+ rescue ArgumentError => e
1838
+ {
1839
+ success: false,
1840
+ message: "Invalid tier: #{e.message}\nValid tiers: mini, standard, thinking, pro, max",
1841
+ action: :none
1842
+ }
1843
+ end
1844
+
1845
+ # Subcommand: /thinking max <tier>
1846
+ def cmd_thinking_max(tier)
1847
+ unless tier
1848
+ return {
1849
+ success: false,
1850
+ message: "Usage: /thinking max <tier>\nTiers: mini, standard, thinking, pro, max",
1851
+ action: :none
1852
+ }
1853
+ end
1854
+
1855
+ require_relative "../harness/configuration"
1856
+ require_relative "../harness/thinking_depth_manager"
1857
+
1858
+ config = Aidp::Harness::Configuration.new(@project_dir)
1859
+ manager = Aidp::Harness::ThinkingDepthManager.new(config, root_dir: @project_dir)
1860
+
1861
+ old_max = manager.max_tier
1862
+ manager.max_tier = tier
1863
+
1864
+ {
1865
+ success: true,
1866
+ message: "Max tier changed: #{old_max} โ†’ #{tier}\nCurrent tier: #{manager.current_tier}",
1867
+ action: :max_tier_changed
1868
+ }
1869
+ rescue ArgumentError => e
1870
+ {
1871
+ success: false,
1872
+ message: "Invalid tier: #{e.message}\nValid tiers: mini, standard, thinking, pro, max",
1873
+ action: :none
1874
+ }
1875
+ end
1876
+
1877
+ # Subcommand: /thinking reset
1878
+ def cmd_thinking_reset
1879
+ require_relative "../harness/configuration"
1880
+ require_relative "../harness/thinking_depth_manager"
1881
+
1882
+ config = Aidp::Harness::Configuration.new(@project_dir)
1883
+ manager = Aidp::Harness::ThinkingDepthManager.new(config, root_dir: @project_dir)
1884
+
1885
+ old_tier = manager.current_tier
1886
+ manager.reset_to_default
1887
+
1888
+ {
1889
+ success: true,
1890
+ message: "Thinking tier reset: #{old_tier} โ†’ #{manager.current_tier}\nEscalation count cleared",
1891
+ action: :tier_reset
1892
+ }
1893
+ end
1894
+
1895
+ # Command: /prompt - Inspect and control prompt optimization
1896
+ def cmd_prompt(args)
1897
+ subcommand = args[0]
1898
+
1899
+ case subcommand
1900
+ when "explain"
1901
+ cmd_prompt_explain
1902
+ when "stats"
1903
+ cmd_prompt_stats
1904
+ when "expand"
1905
+ cmd_prompt_expand(args[1])
1906
+ when "reset"
1907
+ cmd_prompt_reset
1908
+ else
1909
+ {
1910
+ success: false,
1911
+ message: "Unknown subcommand: #{subcommand}\nUsage: /prompt <explain|stats|expand|reset>",
1912
+ action: :none
1913
+ }
1914
+ end
1915
+ rescue => e
1916
+ Aidp.log_error("repl_macros", "Failed to execute prompt command", error: e.message)
1917
+ {
1918
+ success: false,
1919
+ message: "Failed to execute prompt command: #{e.message}",
1920
+ action: :none
1921
+ }
1922
+ end
1923
+
1924
+ # Subcommand: /prompt explain
1925
+ # Shows which fragments were selected for the current prompt and why
1926
+ def cmd_prompt_explain
1927
+ require_relative "prompt_manager"
1928
+
1929
+ prompt_manager = PromptManager.new(@project_dir, config: load_config)
1930
+
1931
+ unless prompt_manager.optimization_enabled?
1932
+ return {
1933
+ success: false,
1934
+ message: "Prompt optimization is not enabled. Check your .aidp/config.yml:\n" \
1935
+ "prompt_optimization:\n enabled: true",
1936
+ action: :none
1937
+ }
1938
+ end
1939
+
1940
+ unless prompt_manager.last_optimization_stats
1941
+ return {
1942
+ success: false,
1943
+ message: "No optimization performed yet. Prompt optimization will be used on the next work loop iteration.",
1944
+ action: :none
1945
+ }
1946
+ end
1947
+
1948
+ report = prompt_manager.optimization_report
1949
+ {
1950
+ success: true,
1951
+ message: report,
1952
+ action: :show_optimization_report
1953
+ }
1954
+ end
1955
+
1956
+ # Subcommand: /prompt stats
1957
+ # Shows overall optimizer statistics across all runs
1958
+ def cmd_prompt_stats
1959
+ require_relative "prompt_manager"
1960
+
1961
+ prompt_manager = PromptManager.new(@project_dir, config: load_config)
1962
+
1963
+ unless prompt_manager.optimization_enabled?
1964
+ return {
1965
+ success: false,
1966
+ message: "Prompt optimization is not enabled.",
1967
+ action: :none
1968
+ }
1969
+ end
1970
+
1971
+ stats = prompt_manager.optimizer_stats
1972
+ unless stats
1973
+ return {
1974
+ success: false,
1975
+ message: "No optimization statistics available.",
1976
+ action: :none
1977
+ }
1978
+ end
1979
+
1980
+ lines = []
1981
+ lines << "# Prompt Optimizer Statistics"
1982
+ lines << ""
1983
+ lines << "- **Total Runs**: #{stats[:runs_count]}"
1984
+ lines << "- **Total Fragments Indexed**: #{stats[:total_fragments_indexed]}"
1985
+ lines << "- **Total Fragments Selected**: #{stats[:total_fragments_selected]}"
1986
+ lines << "- **Total Fragments Excluded**: #{stats[:total_fragments_excluded]}"
1987
+ lines << "- **Total Tokens Used**: #{stats[:total_tokens_used]}"
1988
+ lines << "- **Average Fragments/Run**: #{stats[:average_fragments_selected]}"
1989
+ lines << "- **Average Budget Utilization**: #{stats[:average_budget_utilization]}%"
1990
+ lines << "- **Average Optimization Time**: #{stats[:average_optimization_time_ms]}ms"
1991
+
1992
+ {
1993
+ success: true,
1994
+ message: lines.join("\n"),
1995
+ action: :show_optimizer_stats
1996
+ }
1997
+ end
1998
+
1999
+ # Subcommand: /prompt expand <fragment_id>
2000
+ # Adds a specific omitted fragment to the next prompt
2001
+ def cmd_prompt_expand(fragment_id)
2002
+ unless fragment_id
2003
+ return {
2004
+ success: false,
2005
+ message: "Usage: /prompt expand <fragment_id>\nUse /prompt explain to see available fragments",
2006
+ action: :none
2007
+ }
2008
+ end
2009
+
2010
+ # For now, this is a placeholder - full implementation would:
2011
+ # 1. Look up the fragment by ID
2012
+ # 2. Add it to an override list
2013
+ # 3. Include it in the next prompt generation
2014
+ {
2015
+ success: true,
2016
+ message: "Fragment expansion not yet implemented.\n" \
2017
+ "This will be available in a future update to manually include excluded fragments.",
2018
+ action: :feature_not_implemented
2019
+ }
2020
+ end
2021
+
2022
+ # Subcommand: /prompt reset
2023
+ # Clears optimizer cache and resets to default behavior
2024
+ def cmd_prompt_reset
2025
+ require_relative "prompt_manager"
2026
+
2027
+ prompt_manager = PromptManager.new(@project_dir, config: load_config)
2028
+
2029
+ unless prompt_manager.optimization_enabled?
2030
+ return {
2031
+ success: false,
2032
+ message: "Prompt optimization is not enabled.",
2033
+ action: :none
2034
+ }
2035
+ end
2036
+
2037
+ prompt_manager.optimizer.clear_cache
2038
+
2039
+ {
2040
+ success: true,
2041
+ message: "Optimizer cache cleared. Next prompt will use fresh indexing.",
2042
+ action: :optimizer_reset
2043
+ }
2044
+ end
2045
+
2046
+ # Manage persistent tasklist
2047
+ def cmd_tasks(args)
2048
+ tasklist = PersistentTasklist.new(@project_dir)
2049
+ subcommand = args[0]
2050
+
2051
+ case subcommand
2052
+ when "list", nil
2053
+ cmd_tasks_list(tasklist, args[1])
2054
+ when "show"
2055
+ cmd_tasks_show(tasklist, args[1])
2056
+ when "done"
2057
+ cmd_tasks_done(tasklist, args[1])
2058
+ when "abandon"
2059
+ cmd_tasks_abandon(tasklist, args[1], args[2..]&.join(" "))
2060
+ when "stats"
2061
+ cmd_tasks_stats(tasklist)
2062
+ else
2063
+ {
2064
+ success: false,
2065
+ message: "Unknown subcommand: #{subcommand}. Use: list, show, done, abandon, stats",
2066
+ action: :none
2067
+ }
2068
+ end
2069
+ rescue => e
2070
+ Aidp.log_error("repl_macros", "Tasks command failed", error: e.message)
2071
+ {success: false, message: "Error: #{e.message}", action: :none}
2072
+ end
2073
+
2074
+ private
2075
+
2076
+ # List tasks with optional status filter
2077
+ def cmd_tasks_list(tasklist, status_filter = nil)
2078
+ status = status_filter&.to_sym
2079
+ tasks = status ? tasklist.all(status: status) : tasklist.all
2080
+
2081
+ if tasks.empty?
2082
+ message = status ? "No #{status} tasks found." : "No tasks found."
2083
+ return {success: true, message: message, action: :display}
2084
+ end
2085
+
2086
+ # Group by status
2087
+ by_status = tasks.group_by(&:status)
2088
+ output = []
2089
+
2090
+ [:pending, :in_progress, :done, :abandoned].each do |st|
2091
+ next unless by_status[st]
2092
+ next if status && st != status # Skip if filtering by specific status
2093
+
2094
+ output << ""
2095
+ output << "#{st.to_s.upcase.tr("_", " ")} (#{by_status[st].size})"
2096
+ output << "=" * 50
2097
+
2098
+ by_status[st].each do |task|
2099
+ priority_icon = case task.priority
2100
+ when :high then "โš ๏ธ "
2101
+ when :medium then "โ—‹ "
2102
+ when :low then "ยท "
2103
+ end
2104
+
2105
+ age = ((Time.now - task.created_at) / 86400).to_i
2106
+ age_str = (age > 0) ? " (#{age}d ago)" : " (today)"
2107
+
2108
+ output << " #{priority_icon}[#{task.id}] #{task.description}#{age_str}"
2109
+ end
2110
+ end
2111
+
2112
+ {
2113
+ success: true,
2114
+ message: output.join("\n"),
2115
+ action: :display
2116
+ }
2117
+ end
2118
+
2119
+ # Show detailed information about a specific task
2120
+ def cmd_tasks_show(tasklist, task_id)
2121
+ return {success: false, message: "Usage: /tasks show <task_id>", action: :none} unless task_id
2122
+
2123
+ task = tasklist.find(task_id)
2124
+ unless task
2125
+ return {success: false, message: "Task not found: #{task_id}", action: :none}
2126
+ end
2127
+
2128
+ output = []
2129
+ output << ""
2130
+ output << "Task Details:"
2131
+ output << "=" * 50
2132
+ output << "ID: #{task.id}"
2133
+ output << "Description: #{task.description}"
2134
+ output << "Status: #{task.status}"
2135
+ output << "Priority: #{task.priority}"
2136
+ output << "Created: #{task.created_at}"
2137
+ output << "Updated: #{task.updated_at}"
2138
+ output << "Session: #{task.session}" if task.session
2139
+ output << "Context: #{task.discovered_during}" if task.discovered_during
2140
+ output << "Started: #{task.started_at}" if task.started_at
2141
+ output << "Completed: #{task.completed_at}" if task.completed_at
2142
+ output << "Abandoned: #{task.abandoned_at} (#{task.abandoned_reason})" if task.abandoned_at
2143
+ output << "Tags: #{task.tags.join(", ")}" if task.tags&.any?
2144
+
2145
+ {
2146
+ success: true,
2147
+ message: output.join("\n"),
2148
+ action: :display
2149
+ }
2150
+ end
2151
+
2152
+ # Mark a task as done
2153
+ def cmd_tasks_done(tasklist, task_id)
2154
+ return {success: false, message: "Usage: /tasks done <task_id>", action: :none} unless task_id
2155
+
2156
+ task = tasklist.update_status(task_id, :done)
2157
+ {
2158
+ success: true,
2159
+ message: "โœ“ Task marked as done: #{task.description}",
2160
+ action: :display
2161
+ }
2162
+ rescue PersistentTasklist::TaskNotFoundError
2163
+ {success: false, message: "Task not found: #{task_id}", action: :none}
2164
+ end
2165
+
2166
+ # Abandon a task with optional reason
2167
+ def cmd_tasks_abandon(tasklist, task_id, reason = nil)
2168
+ return {success: false, message: "Usage: /tasks abandon <task_id> [reason]", action: :none} unless task_id
2169
+
2170
+ task = tasklist.update_status(task_id, :abandoned, reason: reason)
2171
+ message = "โœ— Task abandoned: #{task.description}"
2172
+ message += " (Reason: #{reason})" if reason
2173
+
2174
+ {
2175
+ success: true,
2176
+ message: message,
2177
+ action: :display
2178
+ }
2179
+ rescue PersistentTasklist::TaskNotFoundError
2180
+ {success: false, message: "Task not found: #{task_id}", action: :none}
2181
+ end
2182
+
2183
+ # Show task statistics
2184
+ def cmd_tasks_stats(tasklist)
2185
+ counts = tasklist.counts
2186
+
2187
+ output = []
2188
+ output << ""
2189
+ output << "Task Statistics:"
2190
+ output << "=" * 50
2191
+ output << "Total: #{counts[:total]}"
2192
+ output << "Pending: #{counts[:pending]}"
2193
+ output << "In Progress: #{counts[:in_progress]}"
2194
+ output << "Done: #{counts[:done]}"
2195
+ output << "Abandoned: #{counts[:abandoned]}"
2196
+
2197
+ {
2198
+ success: true,
2199
+ message: output.join("\n"),
2200
+ action: :display
2201
+ }
2202
+ end
2203
+
2204
+ private
2205
+
2206
+ # Load configuration for prompt commands
2207
+ def load_config
2208
+ require_relative "../harness/configuration"
2209
+ Aidp::Harness::Configuration.new(@project_dir)
2210
+ end
1492
2211
  end
1493
2212
  end
1494
2213
  end