task-agent 1.0.0__tar.gz → 1.0.2__tar.gz

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 (70) hide show
  1. {task_agent-1.0.0 → task_agent-1.0.2}/PKG-INFO +1 -1
  2. {task_agent-1.0.0 → task_agent-1.0.2}/pyproject.toml +2 -2
  3. {task_agent-1.0.0 → task_agent-1.0.2}/src/taskagent/cli.py +84 -36
  4. {task_agent-1.0.0 → task_agent-1.0.2}/tests/test_cli.py +49 -0
  5. {task_agent-1.0.0 → task_agent-1.0.2}/uv.lock +1 -1
  6. {task_agent-1.0.0 → task_agent-1.0.2}/.env +0 -0
  7. {task_agent-1.0.0 → task_agent-1.0.2}/.gemini/settings.json +0 -0
  8. {task_agent-1.0.0 → task_agent-1.0.2}/.github/workflows/ci.yml +0 -0
  9. {task_agent-1.0.0 → task_agent-1.0.2}/.github/workflows/publish.yml +0 -0
  10. {task_agent-1.0.0 → task_agent-1.0.2}/.gitignore +0 -0
  11. {task_agent-1.0.0 → task_agent-1.0.2}/.mise.toml +0 -0
  12. {task_agent-1.0.0 → task_agent-1.0.2}/.pre-commit-config.yaml +0 -0
  13. {task_agent-1.0.0 → task_agent-1.0.2}/.python-version +0 -0
  14. {task_agent-1.0.0 → task_agent-1.0.2}/.ta/agents/agy-cli/meta.toml +0 -0
  15. {task_agent-1.0.0 → task_agent-1.0.2}/.ta/agents/gh/meta.toml +0 -0
  16. {task_agent-1.0.0 → task_agent-1.0.2}/.ta/agents/minimal/meta.toml +0 -0
  17. {task_agent-1.0.0 → task_agent-1.0.2}/.ta/agents/uat-aws/meta.toml +0 -0
  18. {task_agent-1.0.0 → task_agent-1.0.2}/.ta/worker +0 -0
  19. {task_agent-1.0.0 → task_agent-1.0.2}/CLAUDE.md +0 -0
  20. {task_agent-1.0.0 → task_agent-1.0.2}/GEMINI.md +0 -0
  21. {task_agent-1.0.0 → task_agent-1.0.2}/Makefile +0 -0
  22. {task_agent-1.0.0 → task_agent-1.0.2}/README.md +0 -0
  23. {task_agent-1.0.0 → task_agent-1.0.2}/agent.env +0 -0
  24. {task_agent-1.0.0 → task_agent-1.0.2}/docs/README.md +0 -0
  25. {task_agent-1.0.0 → task_agent-1.0.2}/docs/architecture/README.md +0 -0
  26. {task_agent-1.0.0 → task_agent-1.0.2}/docs/chats/gemini-conversation-1773283034591.json +0 -0
  27. {task_agent-1.0.0 → task_agent-1.0.2}/docs/chats/project-init-to-v0.1.46.md +0 -0
  28. {task_agent-1.0.0 → task_agent-1.0.2}/docs/cli/README.md +0 -0
  29. {task_agent-1.0.0 → task_agent-1.0.2}/docs/development/README.md +0 -0
  30. {task_agent-1.0.0 → task_agent-1.0.2}/docs/task-agents.md +0 -0
  31. {task_agent-1.0.0 → task_agent-1.0.2}/docs/workflow/README.md +0 -0
  32. {task_agent-1.0.0 → task_agent-1.0.2}/scripts/clean_duplicates.py +0 -0
  33. {task_agent-1.0.0 → task_agent-1.0.2}/scripts/migrate_tasks.py +0 -0
  34. {task_agent-1.0.0 → task_agent-1.0.2}/scripts/recover_dates.py +0 -0
  35. {task_agent-1.0.0 → task_agent-1.0.2}/sidecars/adk-worker/.env.example +0 -0
  36. {task_agent-1.0.0 → task_agent-1.0.2}/sidecars/adk-worker/README.md +0 -0
  37. {task_agent-1.0.0 → task_agent-1.0.2}/sidecars/adk-worker/instructions/manager.md +0 -0
  38. {task_agent-1.0.0 → task_agent-1.0.2}/sidecars/adk-worker/instructions/validator.md +0 -0
  39. {task_agent-1.0.0 → task_agent-1.0.2}/sidecars/adk-worker/instructions/worker.md +0 -0
  40. {task_agent-1.0.0 → task_agent-1.0.2}/sidecars/adk-worker/profiles/default.json +0 -0
  41. {task_agent-1.0.0 → task_agent-1.0.2}/sidecars/adk-worker/pyproject.toml +0 -0
  42. {task_agent-1.0.0 → task_agent-1.0.2}/sidecars/adk-worker/uv.lock +0 -0
  43. {task_agent-1.0.0 → task_agent-1.0.2}/sidecars/adk-worker/worker.py +0 -0
  44. {task_agent-1.0.0 → task_agent-1.0.2}/src/taskagent/__init__.py +0 -0
  45. {task_agent-1.0.0 → task_agent-1.0.2}/src/taskagent/__main__.py +0 -0
  46. {task_agent-1.0.0 → task_agent-1.0.2}/src/taskagent/agent.py +0 -0
  47. {task_agent-1.0.0 → task_agent-1.0.2}/src/taskagent/audit.py +0 -0
  48. {task_agent-1.0.0 → task_agent-1.0.2}/src/taskagent/config.py +0 -0
  49. {task_agent-1.0.0 → task_agent-1.0.2}/src/taskagent/discovery.py +0 -0
  50. {task_agent-1.0.0 → task_agent-1.0.2}/src/taskagent/manager.py +0 -0
  51. {task_agent-1.0.0 → task_agent-1.0.2}/src/taskagent/mcp.py +0 -0
  52. {task_agent-1.0.0 → task_agent-1.0.2}/src/taskagent/models/__init__.py +0 -0
  53. {task_agent-1.0.0 → task_agent-1.0.2}/src/taskagent/models/issue.py +0 -0
  54. {task_agent-1.0.0 → task_agent-1.0.2}/src/taskagent/models/metric.py +0 -0
  55. {task_agent-1.0.0 → task_agent-1.0.2}/src/taskagent/plugins/__init__.py +0 -0
  56. {task_agent-1.0.0 → task_agent-1.0.2}/src/taskagent/plugins/github.py +0 -0
  57. {task_agent-1.0.0 → task_agent-1.0.2}/src/taskagent/templates.py +0 -0
  58. {task_agent-1.0.0 → task_agent-1.0.2}/test-dir/opencode.json +0 -0
  59. {task_agent-1.0.0 → task_agent-1.0.2}/tests/conftest.py +0 -0
  60. {task_agent-1.0.0 → task_agent-1.0.2}/tests/e2e/test_agent_sandboxing.sh +0 -0
  61. {task_agent-1.0.0 → task_agent-1.0.2}/tests/test_agent.py +0 -0
  62. {task_agent-1.0.0 → task_agent-1.0.2}/tests/test_agy_cli_worker.py +0 -0
  63. {task_agent-1.0.0 → task_agent-1.0.2}/tests/test_cli_windows.py +0 -0
  64. {task_agent-1.0.0 → task_agent-1.0.2}/tests/test_discovery.py +0 -0
  65. {task_agent-1.0.0 → task_agent-1.0.2}/tests/test_manager.py +0 -0
  66. {task_agent-1.0.0 → task_agent-1.0.2}/tests/test_manager_git.py +0 -0
  67. {task_agent-1.0.0 → task_agent-1.0.2}/tests/test_mcp.py +0 -0
  68. {task_agent-1.0.0 → task_agent-1.0.2}/tests/test_metrics.py +0 -0
  69. {task_agent-1.0.0 → task_agent-1.0.2}/tests/test_migration.py +0 -0
  70. {task_agent-1.0.0 → task_agent-1.0.2}/tests/test_templates.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: task-agent
3
- Version: 1.0.0
3
+ Version: 1.0.2
4
4
  Summary: A prioritized, file-based task queue for autonomous agentic workers
5
5
  Author-email: Mark Stouffer <1802850+InTEGr8or@users.noreply.github.com>
6
6
  License: MIT
@@ -1,6 +1,6 @@
1
1
  [project]
2
2
  name = "task-agent"
3
- version = "1.0.0"
3
+ version = "1.0.2"
4
4
  description = "A prioritized, file-based task queue for autonomous agentic workers"
5
5
  readme = "README.md"
6
6
  requires-python = ">=3.12"
@@ -39,7 +39,7 @@ dev = [
39
39
  ]
40
40
 
41
41
  [tool.bumpversion]
42
- current_version = "1.0.0"
42
+ current_version = "1.0.2"
43
43
  parse = "(?P<major>\\d+)\\.(?P<minor>\\d+)\\.(?P<patch>\\d+)"
44
44
  serialize = ["{major}.{minor}.{patch}"]
45
45
  search = "version = \"{current_version}\""
@@ -3505,49 +3505,94 @@ def cmd_triage(
3505
3505
  target_issue = indexed_issues[sibling_idx][0]
3506
3506
 
3507
3507
  if target_issue:
3508
+ live.stop()
3508
3509
  try:
3509
- # Check transitive dependencies recursively to avoid redundancy
3510
- all_issues = manager.load_mission()
3511
-
3512
- def is_ancestor(ancestor_slug, desc_slug):
3513
- slug_to_issue = {i.slug: i for i in all_issues}
3514
- if desc_slug not in slug_to_issue:
3515
- return False
3516
- todo = list(slug_to_issue[desc_slug].dependencies)
3517
- visited = set(todo)
3518
- while todo:
3519
- curr = todo.pop()
3520
- if curr == ancestor_slug:
3521
- return True
3522
- if curr in slug_to_issue:
3523
- for dep in slug_to_issue[curr].dependencies:
3524
- if dep not in visited:
3525
- visited.add(dep)
3526
- todo.append(dep)
3527
- return False
3528
-
3529
- # Add dependency on target_issue
3530
- manager.add_dependency(
3531
- current_issue.slug, target_issue.slug
3532
- )
3533
-
3534
- # Remove redundant direct dependencies that are transitively covered
3535
- for dep in list(current_issue.dependencies):
3536
- if dep != target_issue.slug and is_ancestor(
3537
- dep, target_issue.slug
3538
- ):
3539
- manager.remove_dependency(current_issue.slug, dep)
3510
+ choice = questionary.select(
3511
+ f"Link '{current_issue.slug}' to '{target_issue.slug}' as:",
3512
+ choices=[
3513
+ f"Subtask of (Hierarchy: parent '{target_issue.slug}' is blocked until child '{current_issue.slug}' is done)",
3514
+ f"Blocked by (Ordering: child '{current_issue.slug}' cannot start/finish until parent '{target_issue.slug}' is done)",
3515
+ "Cancel",
3516
+ ],
3517
+ ).ask()
3518
+ except (EOFError, Exception):
3519
+ choice = "Blocked by"
3520
+
3521
+ if choice and not choice.startswith("Cancel"):
3522
+ try:
3523
+ if choice.startswith("Subtask of"):
3524
+ # If it was blocked_by, remove to avoid redundancy
3525
+ if target_issue.slug in current_issue.blocked_by:
3526
+ manager.remove_dependency(
3527
+ current_issue.slug,
3528
+ target_issue.slug,
3529
+ )
3530
+ manager.update_subtask_of(
3531
+ current_issue.slug, target_issue.slug
3532
+ )
3533
+ else:
3534
+ # Blocked by
3535
+ # If it was subtask_of, remove to avoid conflicts
3536
+ if current_issue.subtask_of == target_issue.slug:
3537
+ manager.update_subtask_of(
3538
+ current_issue.slug, None
3539
+ )
3540
+
3541
+ # Check transitive dependencies recursively to avoid redundancy
3542
+ all_issues = manager.load_mission()
3543
+
3544
+ def is_ancestor(ancestor_slug, desc_slug):
3545
+ slug_to_issue = {i.slug: i for i in all_issues}
3546
+ if desc_slug not in slug_to_issue:
3547
+ return False
3548
+ todo = list(
3549
+ slug_to_issue[desc_slug].dependencies
3550
+ )
3551
+ visited = set(todo)
3552
+ while todo:
3553
+ curr = todo.pop()
3554
+ if curr == ancestor_slug:
3555
+ return True
3556
+ if curr in slug_to_issue:
3557
+ for dep in slug_to_issue[
3558
+ curr
3559
+ ].dependencies:
3560
+ if dep not in visited:
3561
+ visited.add(dep)
3562
+ todo.append(dep)
3563
+ return False
3564
+
3565
+ manager.add_dependency(
3566
+ current_issue.slug, target_issue.slug
3567
+ )
3568
+
3569
+ # Remove redundant direct dependencies that are transitively covered
3570
+ for dep in list(current_issue.dependencies):
3571
+ if dep != target_issue.slug and is_ancestor(
3572
+ dep, target_issue.slug
3573
+ ):
3574
+ manager.remove_dependency(
3575
+ current_issue.slug, dep
3576
+ )
3540
3577
 
3541
- issues = get_display_issues(search_query, show_completed)
3542
- indexed_issues = build_hierarchy(issues)
3543
- except Exception as e:
3544
- console.print(f"[red]Error: {e}[/red]")
3578
+ issues = get_display_issues(
3579
+ search_query, show_completed
3580
+ )
3581
+ indexed_issues = build_hierarchy(issues)
3582
+ except Exception as e:
3583
+ console.print(f"[red]Error: {e}[/red]")
3584
+ questionary.press_any_key_to_continue().ask()
3585
+ live.start()
3545
3586
  elif key == "h" and not show_completed: # remove dependency on above
3546
3587
  if cursor > 0:
3547
3588
  current_issue = indexed_issues[cursor][0]
3548
3589
  above_issue = indexed_issues[cursor - 1][0]
3549
3590
  try:
3550
- if above_issue.slug in current_issue.dependencies:
3591
+ updated = False
3592
+ if current_issue.subtask_of == above_issue.slug:
3593
+ manager.update_subtask_of(current_issue.slug, None)
3594
+ updated = True
3595
+ elif above_issue.slug in current_issue.blocked_by:
3551
3596
  parents = list(above_issue.dependencies)
3552
3597
  manager.remove_dependency(
3553
3598
  current_issue.slug, above_issue.slug
@@ -3555,11 +3600,14 @@ def cmd_triage(
3555
3600
  # Move up: inherit the parent's dependencies
3556
3601
  for parent in parents:
3557
3602
  manager.add_dependency(current_issue.slug, parent)
3603
+ updated = True
3558
3604
 
3605
+ if updated:
3559
3606
  issues = get_display_issues(search_query, show_completed)
3560
3607
  indexed_issues = build_hierarchy(issues)
3561
3608
  except Exception as e:
3562
3609
  console.print(f"[red]Error: {e}[/red]")
3610
+ questionary.press_any_key_to_continue().ask()
3563
3611
 
3564
3612
 
3565
3613
  def display_overview(console: Console, manager: TaskAgent):
@@ -575,3 +575,52 @@ def test_cmd_triage_depends_on_above(manager, monkeypatch):
575
575
  t3_updated = manager.find_issue_file("task-3")
576
576
  assert "task-1" in manager.extract_deps(t3_updated)
577
577
  assert "task-2" not in manager.extract_deps(t3_updated)
578
+
579
+
580
+ def test_cmd_triage_subtask_of(manager, monkeypatch):
581
+ from taskagent.cli import cmd_triage
582
+ from unittest.mock import MagicMock, patch
583
+
584
+ manager.create_issue("Task 1")
585
+ manager.create_issue("Task 2")
586
+
587
+ mock_console = MagicMock()
588
+
589
+ # We mock questionary.select().ask() to return "Subtask of..."
590
+ mock_select = MagicMock()
591
+ mock_select.ask.return_value = "Subtask of (Hierarchy...)"
592
+
593
+ keys = ["j", "l", "q"]
594
+
595
+ def mock_get_key():
596
+ if keys:
597
+ return keys.pop(0)
598
+ return "q"
599
+
600
+ with (
601
+ patch("taskagent.cli.get_key", mock_get_key),
602
+ patch("taskagent.cli.Live"),
603
+ patch("questionary.select", return_value=mock_select),
604
+ ):
605
+ cmd_triage(mock_console, manager)
606
+
607
+ issue_t2 = manager.load_mission()[1]
608
+ assert issue_t2.subtask_of == "task-1"
609
+ assert "task-1" not in issue_t2.blocked_by
610
+
611
+ # Test 2: Unlink subtask relation using 'h' key
612
+ keys = ["j", "h", "q"]
613
+
614
+ def mock_get_key_unlink():
615
+ if keys:
616
+ return keys.pop(0)
617
+ return "q"
618
+
619
+ with (
620
+ patch("taskagent.cli.get_key", mock_get_key_unlink),
621
+ patch("taskagent.cli.Live"),
622
+ ):
623
+ cmd_triage(mock_console, manager)
624
+
625
+ issue_t2_unlinked = manager.load_mission()[1]
626
+ assert issue_t2_unlinked.subtask_of is None
@@ -1135,7 +1135,7 @@ wheels = [
1135
1135
 
1136
1136
  [[package]]
1137
1137
  name = "task-agent"
1138
- version = "1.0.0"
1138
+ version = "1.0.2"
1139
1139
  source = { editable = "." }
1140
1140
  dependencies = [
1141
1141
  { name = "githubkit" },
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes