task-agent 1.0.2__tar.gz → 1.0.3__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.
- {task_agent-1.0.2 → task_agent-1.0.3}/PKG-INFO +1 -1
- {task_agent-1.0.2 → task_agent-1.0.3}/pyproject.toml +2 -2
- {task_agent-1.0.2 → task_agent-1.0.3}/src/taskagent/cli.py +59 -0
- {task_agent-1.0.2 → task_agent-1.0.3}/src/taskagent/mcp.py +69 -2
- {task_agent-1.0.2 → task_agent-1.0.3}/tests/test_cli.py +29 -0
- {task_agent-1.0.2 → task_agent-1.0.3}/tests/test_mcp.py +17 -0
- {task_agent-1.0.2 → task_agent-1.0.3}/uv.lock +1 -1
- {task_agent-1.0.2 → task_agent-1.0.3}/.env +0 -0
- {task_agent-1.0.2 → task_agent-1.0.3}/.gemini/settings.json +0 -0
- {task_agent-1.0.2 → task_agent-1.0.3}/.github/workflows/ci.yml +0 -0
- {task_agent-1.0.2 → task_agent-1.0.3}/.github/workflows/publish.yml +0 -0
- {task_agent-1.0.2 → task_agent-1.0.3}/.gitignore +0 -0
- {task_agent-1.0.2 → task_agent-1.0.3}/.mise.toml +0 -0
- {task_agent-1.0.2 → task_agent-1.0.3}/.pre-commit-config.yaml +0 -0
- {task_agent-1.0.2 → task_agent-1.0.3}/.python-version +0 -0
- {task_agent-1.0.2 → task_agent-1.0.3}/.ta/agents/agy-cli/meta.toml +0 -0
- {task_agent-1.0.2 → task_agent-1.0.3}/.ta/agents/gh/meta.toml +0 -0
- {task_agent-1.0.2 → task_agent-1.0.3}/.ta/agents/minimal/meta.toml +0 -0
- {task_agent-1.0.2 → task_agent-1.0.3}/.ta/agents/uat-aws/meta.toml +0 -0
- {task_agent-1.0.2 → task_agent-1.0.3}/.ta/worker +0 -0
- {task_agent-1.0.2 → task_agent-1.0.3}/CLAUDE.md +0 -0
- {task_agent-1.0.2 → task_agent-1.0.3}/GEMINI.md +0 -0
- {task_agent-1.0.2 → task_agent-1.0.3}/Makefile +0 -0
- {task_agent-1.0.2 → task_agent-1.0.3}/README.md +0 -0
- {task_agent-1.0.2 → task_agent-1.0.3}/agent.env +0 -0
- {task_agent-1.0.2 → task_agent-1.0.3}/docs/README.md +0 -0
- {task_agent-1.0.2 → task_agent-1.0.3}/docs/architecture/README.md +0 -0
- {task_agent-1.0.2 → task_agent-1.0.3}/docs/chats/gemini-conversation-1773283034591.json +0 -0
- {task_agent-1.0.2 → task_agent-1.0.3}/docs/chats/project-init-to-v0.1.46.md +0 -0
- {task_agent-1.0.2 → task_agent-1.0.3}/docs/cli/README.md +0 -0
- {task_agent-1.0.2 → task_agent-1.0.3}/docs/development/README.md +0 -0
- {task_agent-1.0.2 → task_agent-1.0.3}/docs/task-agents.md +0 -0
- {task_agent-1.0.2 → task_agent-1.0.3}/docs/workflow/README.md +0 -0
- {task_agent-1.0.2 → task_agent-1.0.3}/scripts/clean_duplicates.py +0 -0
- {task_agent-1.0.2 → task_agent-1.0.3}/scripts/migrate_tasks.py +0 -0
- {task_agent-1.0.2 → task_agent-1.0.3}/scripts/recover_dates.py +0 -0
- {task_agent-1.0.2 → task_agent-1.0.3}/sidecars/adk-worker/.env.example +0 -0
- {task_agent-1.0.2 → task_agent-1.0.3}/sidecars/adk-worker/README.md +0 -0
- {task_agent-1.0.2 → task_agent-1.0.3}/sidecars/adk-worker/instructions/manager.md +0 -0
- {task_agent-1.0.2 → task_agent-1.0.3}/sidecars/adk-worker/instructions/validator.md +0 -0
- {task_agent-1.0.2 → task_agent-1.0.3}/sidecars/adk-worker/instructions/worker.md +0 -0
- {task_agent-1.0.2 → task_agent-1.0.3}/sidecars/adk-worker/profiles/default.json +0 -0
- {task_agent-1.0.2 → task_agent-1.0.3}/sidecars/adk-worker/pyproject.toml +0 -0
- {task_agent-1.0.2 → task_agent-1.0.3}/sidecars/adk-worker/uv.lock +0 -0
- {task_agent-1.0.2 → task_agent-1.0.3}/sidecars/adk-worker/worker.py +0 -0
- {task_agent-1.0.2 → task_agent-1.0.3}/src/taskagent/__init__.py +0 -0
- {task_agent-1.0.2 → task_agent-1.0.3}/src/taskagent/__main__.py +0 -0
- {task_agent-1.0.2 → task_agent-1.0.3}/src/taskagent/agent.py +0 -0
- {task_agent-1.0.2 → task_agent-1.0.3}/src/taskagent/audit.py +0 -0
- {task_agent-1.0.2 → task_agent-1.0.3}/src/taskagent/config.py +0 -0
- {task_agent-1.0.2 → task_agent-1.0.3}/src/taskagent/discovery.py +0 -0
- {task_agent-1.0.2 → task_agent-1.0.3}/src/taskagent/manager.py +0 -0
- {task_agent-1.0.2 → task_agent-1.0.3}/src/taskagent/models/__init__.py +0 -0
- {task_agent-1.0.2 → task_agent-1.0.3}/src/taskagent/models/issue.py +0 -0
- {task_agent-1.0.2 → task_agent-1.0.3}/src/taskagent/models/metric.py +0 -0
- {task_agent-1.0.2 → task_agent-1.0.3}/src/taskagent/plugins/__init__.py +0 -0
- {task_agent-1.0.2 → task_agent-1.0.3}/src/taskagent/plugins/github.py +0 -0
- {task_agent-1.0.2 → task_agent-1.0.3}/src/taskagent/templates.py +0 -0
- {task_agent-1.0.2 → task_agent-1.0.3}/test-dir/opencode.json +0 -0
- {task_agent-1.0.2 → task_agent-1.0.3}/tests/conftest.py +0 -0
- {task_agent-1.0.2 → task_agent-1.0.3}/tests/e2e/test_agent_sandboxing.sh +0 -0
- {task_agent-1.0.2 → task_agent-1.0.3}/tests/test_agent.py +0 -0
- {task_agent-1.0.2 → task_agent-1.0.3}/tests/test_agy_cli_worker.py +0 -0
- {task_agent-1.0.2 → task_agent-1.0.3}/tests/test_cli_windows.py +0 -0
- {task_agent-1.0.2 → task_agent-1.0.3}/tests/test_discovery.py +0 -0
- {task_agent-1.0.2 → task_agent-1.0.3}/tests/test_manager.py +0 -0
- {task_agent-1.0.2 → task_agent-1.0.3}/tests/test_manager_git.py +0 -0
- {task_agent-1.0.2 → task_agent-1.0.3}/tests/test_metrics.py +0 -0
- {task_agent-1.0.2 → task_agent-1.0.3}/tests/test_migration.py +0 -0
- {task_agent-1.0.2 → task_agent-1.0.3}/tests/test_templates.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
[project]
|
|
2
2
|
name = "task-agent"
|
|
3
|
-
version = "1.0.
|
|
3
|
+
version = "1.0.3"
|
|
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.
|
|
42
|
+
current_version = "1.0.3"
|
|
43
43
|
parse = "(?P<major>\\d+)\\.(?P<minor>\\d+)\\.(?P<patch>\\d+)"
|
|
44
44
|
serialize = ["{major}.{minor}.{patch}"]
|
|
45
45
|
search = "version = \"{current_version}\""
|
|
@@ -1745,8 +1745,62 @@ def cmd_new(
|
|
|
1745
1745
|
interactive: bool = False,
|
|
1746
1746
|
blocked_by: Optional[str] = None,
|
|
1747
1747
|
subtask_of: Optional[str] = None,
|
|
1748
|
+
bulk: Optional[str] = None,
|
|
1748
1749
|
):
|
|
1749
1750
|
"""Create a new issue."""
|
|
1751
|
+
if bulk:
|
|
1752
|
+
try:
|
|
1753
|
+
if bulk == "-":
|
|
1754
|
+
raw_data = sys.stdin.read()
|
|
1755
|
+
else:
|
|
1756
|
+
raw_data = Path(bulk).read_text(encoding="utf-8")
|
|
1757
|
+
|
|
1758
|
+
tasks = json.loads(raw_data)
|
|
1759
|
+
if not isinstance(tasks, list):
|
|
1760
|
+
raise ValueError("Bulk JSON must be a list/array of task objects.")
|
|
1761
|
+
|
|
1762
|
+
for idx, t in enumerate(tasks):
|
|
1763
|
+
t_title = t.get("title")
|
|
1764
|
+
t_criteria = t.get("completion_criteria")
|
|
1765
|
+
if not t_title:
|
|
1766
|
+
console.print(
|
|
1767
|
+
f"[red]Error at task {idx}: 'title' is required.[/red]"
|
|
1768
|
+
)
|
|
1769
|
+
continue
|
|
1770
|
+
if not t_criteria:
|
|
1771
|
+
console.print(
|
|
1772
|
+
f"[red]Error at task '{t_title}' (index {idx}): 'completion_criteria' is required.[/red]"
|
|
1773
|
+
)
|
|
1774
|
+
continue
|
|
1775
|
+
|
|
1776
|
+
t_body = t.get("body", "")
|
|
1777
|
+
t_draft = t.get("draft", draft)
|
|
1778
|
+
t_blocked_by = t.get("blocked_by") or t.get("depends_on")
|
|
1779
|
+
t_subtask_of = t.get("subtask_of")
|
|
1780
|
+
t_as_dir = t.get("as_dir", as_dir)
|
|
1781
|
+
|
|
1782
|
+
issue = manager.create_issue(
|
|
1783
|
+
title=t_title,
|
|
1784
|
+
body=t_body,
|
|
1785
|
+
draft=t_draft,
|
|
1786
|
+
depends_on=t_blocked_by,
|
|
1787
|
+
as_dir=t_as_dir,
|
|
1788
|
+
completion_criteria=t_criteria,
|
|
1789
|
+
blocked_by=t_blocked_by,
|
|
1790
|
+
subtask_of=t_subtask_of,
|
|
1791
|
+
)
|
|
1792
|
+
console.print(
|
|
1793
|
+
f"[bold green]Created new issue: {issue.slug}[/bold green]"
|
|
1794
|
+
)
|
|
1795
|
+
issue_file = manager.find_issue_file(issue.slug)
|
|
1796
|
+
console.print(f"File: {issue_file}")
|
|
1797
|
+
if issue.subtask_of:
|
|
1798
|
+
console.print(f"Subtask of: {issue.subtask_of}")
|
|
1799
|
+
return
|
|
1800
|
+
except Exception as e:
|
|
1801
|
+
console.print(f"[red]Error processing bulk tasks: {e}[/red]")
|
|
1802
|
+
sys.exit(1)
|
|
1803
|
+
|
|
1750
1804
|
if interactive:
|
|
1751
1805
|
editor = get_editor()
|
|
1752
1806
|
slug = manager.slugify(title or "new-task")
|
|
@@ -4045,6 +4099,10 @@ Usage:
|
|
|
4045
4099
|
"--subtask-of",
|
|
4046
4100
|
help="Slug of the parent task this task is a subtask of, e.g. 'cli-consolidation'.",
|
|
4047
4101
|
)
|
|
4102
|
+
new_parser.add_argument(
|
|
4103
|
+
"--bulk",
|
|
4104
|
+
help="Path to a JSON file containing an array of task definitions, or '-' to read JSON from stdin.",
|
|
4105
|
+
)
|
|
4048
4106
|
version_parser = subparsers.add_parser("version")
|
|
4049
4107
|
v_sub = version_parser.add_subparsers(dest="version_command")
|
|
4050
4108
|
p_v = v_sub.add_parser("promote")
|
|
@@ -4202,6 +4260,7 @@ Usage:
|
|
|
4202
4260
|
interactive=args.interactive,
|
|
4203
4261
|
blocked_by=args.blocked_by,
|
|
4204
4262
|
subtask_of=args.subtask_of,
|
|
4263
|
+
bulk=args.bulk,
|
|
4205
4264
|
)
|
|
4206
4265
|
elif args.command == "worktree":
|
|
4207
4266
|
cmd_worktree(console, manager, args)
|
|
@@ -455,8 +455,75 @@ def commit_tasks(message: str = "", push: bool = False) -> str:
|
|
|
455
455
|
return f"Committed: {message}"
|
|
456
456
|
except subprocess.CalledProcessError as e:
|
|
457
457
|
return f"Error: {e.stderr}"
|
|
458
|
-
|
|
459
|
-
|
|
458
|
+
|
|
459
|
+
|
|
460
|
+
@mcp.tool()
|
|
461
|
+
def create_tasks(
|
|
462
|
+
tasks: list[dict],
|
|
463
|
+
) -> str:
|
|
464
|
+
"""Create multiple new tasks in the mission queue at once.
|
|
465
|
+
|
|
466
|
+
Args:
|
|
467
|
+
tasks: A list of task definitions. Each definition is a dictionary that
|
|
468
|
+
can contain the following keys:
|
|
469
|
+
- 'title' (str, required): The title of the task.
|
|
470
|
+
- 'completion_criteria' (str, required): Clear, measurable criteria for task completion.
|
|
471
|
+
- 'body' (str, optional): Detailed description of the task.
|
|
472
|
+
- 'draft' (bool, optional): If True, creates the task in 'draft' status. Default is False (pending).
|
|
473
|
+
- 'blocked_by' (str, optional): Comma-separated list of existing task slugs this task depends on.
|
|
474
|
+
- 'depends_on' (str, optional): Deprecated alias for blocked_by.
|
|
475
|
+
- 'subtask_of' (str, optional): Slug of the parent task this task is a subtask of.
|
|
476
|
+
- 'as_dir' (bool, optional): Create as folder directory. Default is True.
|
|
477
|
+
|
|
478
|
+
Returns:
|
|
479
|
+
A summary report of created task slugs and any errors.
|
|
480
|
+
"""
|
|
481
|
+
manager = get_manager()
|
|
482
|
+
created = []
|
|
483
|
+
errors = []
|
|
484
|
+
|
|
485
|
+
for idx, t in enumerate(tasks):
|
|
486
|
+
title = t.get("title")
|
|
487
|
+
criteria = t.get("completion_criteria")
|
|
488
|
+
if not title:
|
|
489
|
+
errors.append(f"Task at index {idx} is missing required 'title'.")
|
|
490
|
+
continue
|
|
491
|
+
if not criteria:
|
|
492
|
+
errors.append(
|
|
493
|
+
f"Task '{title}' (index {idx}) is missing required 'completion_criteria'."
|
|
494
|
+
)
|
|
495
|
+
continue
|
|
496
|
+
|
|
497
|
+
body = t.get("body", "")
|
|
498
|
+
draft = t.get("draft", False)
|
|
499
|
+
blocked_by = t.get("blocked_by") or t.get("depends_on")
|
|
500
|
+
subtask_of = t.get("subtask_of")
|
|
501
|
+
as_dir = t.get("as_dir", True)
|
|
502
|
+
|
|
503
|
+
try:
|
|
504
|
+
issue = manager.create_issue(
|
|
505
|
+
title=title,
|
|
506
|
+
body=body,
|
|
507
|
+
draft=draft,
|
|
508
|
+
depends_on=blocked_by,
|
|
509
|
+
as_dir=as_dir,
|
|
510
|
+
completion_criteria=criteria,
|
|
511
|
+
blocked_by=blocked_by,
|
|
512
|
+
subtask_of=subtask_of,
|
|
513
|
+
)
|
|
514
|
+
created.append(f"{issue.slug} (Status: {issue.status})")
|
|
515
|
+
except Exception as e:
|
|
516
|
+
errors.append(f"Error creating task '{title}': {e}")
|
|
517
|
+
|
|
518
|
+
report = []
|
|
519
|
+
if created:
|
|
520
|
+
report.append(
|
|
521
|
+
"Successfully created tasks:\n" + "\n".join(f"- {c}" for c in created)
|
|
522
|
+
)
|
|
523
|
+
if errors:
|
|
524
|
+
report.append("Errors encountered:\n" + "\n".join(f"- {err}" for err in errors))
|
|
525
|
+
|
|
526
|
+
return "\n\n".join(report)
|
|
460
527
|
|
|
461
528
|
|
|
462
529
|
def run_mcp_server():
|
|
@@ -75,6 +75,35 @@ def test_cmd_new_dir(manager, temp_issues_dir):
|
|
|
75
75
|
assert issues[0].status == "draft"
|
|
76
76
|
|
|
77
77
|
|
|
78
|
+
def test_cmd_new_bulk(manager, temp_issues_dir, tmp_path):
|
|
79
|
+
console = Console()
|
|
80
|
+
import json
|
|
81
|
+
|
|
82
|
+
tasks_data = [
|
|
83
|
+
{"title": "Bulk 1", "completion_criteria": "CC 1", "body": "Body 1"},
|
|
84
|
+
{"title": "Bulk 2", "completion_criteria": "CC 2", "draft": True},
|
|
85
|
+
]
|
|
86
|
+
|
|
87
|
+
json_file = tmp_path / "bulk_tasks.json"
|
|
88
|
+
json_file.write_text(json.dumps(tasks_data), encoding="utf-8")
|
|
89
|
+
|
|
90
|
+
cmd_new(
|
|
91
|
+
console=console,
|
|
92
|
+
manager=manager,
|
|
93
|
+
title=None,
|
|
94
|
+
body="",
|
|
95
|
+
draft=False,
|
|
96
|
+
bulk=str(json_file),
|
|
97
|
+
)
|
|
98
|
+
|
|
99
|
+
issues = manager.load_mission()
|
|
100
|
+
assert len(issues) == 2
|
|
101
|
+
assert issues[0].slug == "bulk-1"
|
|
102
|
+
assert issues[0].status == "pending"
|
|
103
|
+
assert issues[1].slug == "bulk-2"
|
|
104
|
+
assert issues[1].status == "draft"
|
|
105
|
+
|
|
106
|
+
|
|
78
107
|
def test_cmd_done(manager, temp_issues_dir):
|
|
79
108
|
console = Console()
|
|
80
109
|
cmd_new(console, manager, "Done Task", "Body", draft=False)
|
|
@@ -44,6 +44,22 @@ def test_mcp_create_task(mock_manager):
|
|
|
44
44
|
)
|
|
45
45
|
|
|
46
46
|
|
|
47
|
+
def test_mcp_create_tasks(mock_manager):
|
|
48
|
+
mock_manager.create_issue.return_value = Issue(
|
|
49
|
+
name="Task 1", slug="task-1", status="pending"
|
|
50
|
+
)
|
|
51
|
+
|
|
52
|
+
tasks = [
|
|
53
|
+
{"title": "Task 1", "completion_criteria": "Must pass tests", "body": "Desc 1"},
|
|
54
|
+
{"title": "Task 2", "completion_criteria": "Done 2", "draft": True},
|
|
55
|
+
]
|
|
56
|
+
|
|
57
|
+
result = mcp.create_tasks(tasks)
|
|
58
|
+
assert "Successfully created tasks:" in result
|
|
59
|
+
assert "- task-1 (Status: pending)" in result
|
|
60
|
+
assert mock_manager.create_issue.call_count == 2
|
|
61
|
+
|
|
62
|
+
|
|
47
63
|
def test_mcp_mark_task_active(mock_manager):
|
|
48
64
|
mock_manager.slugify.return_value = "task-1"
|
|
49
65
|
result = mcp.mark_task_active("Task 1")
|
|
@@ -244,6 +260,7 @@ EXPECTED_TOOLS = {
|
|
|
244
260
|
"list_tasks",
|
|
245
261
|
"list_active_tasks",
|
|
246
262
|
"create_task",
|
|
263
|
+
"create_tasks",
|
|
247
264
|
"promote_task",
|
|
248
265
|
"demote_task",
|
|
249
266
|
"mark_task_active",
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|