task-agent 0.1.232__tar.gz → 0.1.246__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 (113) hide show
  1. {task_agent-0.1.232 → task_agent-0.1.246}/Makefile +1 -1
  2. {task_agent-0.1.232 → task_agent-0.1.246}/PKG-INFO +2 -1
  3. task_agent-0.1.246/docs/tasks/completed/2026/add-commit-and-push/README.md +6 -0
  4. task_agent-0.1.246/docs/tasks/completed/2026/add-configuration-file-support-for-github-plugin/README.md +6 -0
  5. task_agent-0.1.246/docs/tasks/completed/2026/integreate-with-github-issues/README.md +16 -0
  6. {task_agent-0.1.232/docs/tasks/pending → task_agent-0.1.246/docs/tasks/completed/2026}/make-copy-slug-available-from-the-history-list/README.md +3 -0
  7. {task_agent-0.1.232/docs/tasks/pending → task_agent-0.1.246/docs/tasks/completed/2026}/search-is-not-working/README.md +3 -0
  8. task_agent-0.1.246/docs/tasks/completed/2026/search-should-include-completed-items-in-the-resultes/README.md +12 -0
  9. task_agent-0.1.246/docs/tasks/completed/2026/ta-list-should-use-the-same-style-template-as-ta-prior/README.md +10 -0
  10. {task_agent-0.1.232 → task_agent-0.1.246}/pyproject.toml +3 -2
  11. {task_agent-0.1.232 → task_agent-0.1.246}/src/taskagent/cli.py +303 -78
  12. {task_agent-0.1.232 → task_agent-0.1.246}/src/taskagent/manager.py +10 -9
  13. task_agent-0.1.246/src/taskagent/models/__init__.py +0 -0
  14. task_agent-0.1.246/src/taskagent/plugins/__init__.py +22 -0
  15. task_agent-0.1.246/src/taskagent/plugins/github.py +114 -0
  16. {task_agent-0.1.232 → task_agent-0.1.246}/uv.lock +91 -1
  17. task_agent-0.1.232/docs/tasks/mission.usv +0 -2
  18. {task_agent-0.1.232 → task_agent-0.1.246}/.gemini/settings.json +0 -0
  19. {task_agent-0.1.232 → task_agent-0.1.246}/.github/workflows/ci.yml +0 -0
  20. {task_agent-0.1.232 → task_agent-0.1.246}/.github/workflows/publish.yml +0 -0
  21. {task_agent-0.1.232 → task_agent-0.1.246}/.gitignore +0 -0
  22. {task_agent-0.1.232 → task_agent-0.1.246}/.mise.toml +0 -0
  23. {task_agent-0.1.232 → task_agent-0.1.246}/.pre-commit-config.yaml +0 -0
  24. {task_agent-0.1.232 → task_agent-0.1.246}/.python-version +0 -0
  25. {task_agent-0.1.232 → task_agent-0.1.246}/.ta/worker +0 -0
  26. {task_agent-0.1.232 → task_agent-0.1.246}/.task-agent/worktree-config.json +0 -0
  27. {task_agent-0.1.232 → task_agent-0.1.246}/GEMINI.md +0 -0
  28. {task_agent-0.1.232 → task_agent-0.1.246}/README.md +0 -0
  29. {task_agent-0.1.232 → task_agent-0.1.246}/docs/README.md +0 -0
  30. {task_agent-0.1.232 → task_agent-0.1.246}/docs/architecture/README.md +0 -0
  31. {task_agent-0.1.232 → task_agent-0.1.246}/docs/chats/gemini-conversation-1773283034591.json +0 -0
  32. {task_agent-0.1.232 → task_agent-0.1.246}/docs/chats/project-init-to-v0.1.46.md +0 -0
  33. {task_agent-0.1.232 → task_agent-0.1.246}/docs/cli/README.md +0 -0
  34. {task_agent-0.1.232 → task_agent-0.1.246}/docs/development/README.md +0 -0
  35. {task_agent-0.1.232/docs/tasks → task_agent-0.1.246/docs/tasks/.task-agent}/datapackage.json +0 -0
  36. {task_agent-0.1.232/docs/issues → task_agent-0.1.246/docs/tasks/.task-agent}/mission.usv +0 -0
  37. {task_agent-0.1.232 → task_agent-0.1.246}/docs/tasks/README.md +0 -0
  38. {task_agent-0.1.232 → task_agent-0.1.246}/docs/tasks/completed/2026/add-a-history-function/README.md +0 -0
  39. {task_agent-0.1.232 → task_agent-0.1.246}/docs/tasks/completed/2026/add-an-interactive-new/README.md +0 -0
  40. {task_agent-0.1.232 → task_agent-0.1.246}/docs/tasks/completed/2026/add-json-switch-to-output-to-json-for-integrations-such-as-nvim.md +0 -0
  41. {task_agent-0.1.232 → task_agent-0.1.246}/docs/tasks/completed/2026/add-search-feature.md +0 -0
  42. {task_agent-0.1.232 → task_agent-0.1.246}/docs/tasks/completed/2026/add-ta-path-command.md +0 -0
  43. {task_agent-0.1.232 → task_agent-0.1.246}/docs/tasks/completed/2026/add-triage-cli-sorting.md +0 -0
  44. {task_agent-0.1.232 → task_agent-0.1.246}/docs/tasks/completed/2026/add-view-and-edit-features-to-triage.md +0 -0
  45. {task_agent-0.1.232 → task_agent-0.1.246}/docs/tasks/completed/2026/agent-reports-mcp-access-issue.md +0 -0
  46. {task_agent-0.1.232 → task_agent-0.1.246}/docs/tasks/completed/2026/allow-optional-role-property-of-the-issuetask.md +0 -0
  47. {task_agent-0.1.232 → task_agent-0.1.246}/docs/tasks/completed/2026/cli-observability-layer/README.md +0 -0
  48. {task_agent-0.1.232 → task_agent-0.1.246}/docs/tasks/completed/2026/create-a-copy-slug-feature-in-ta-prior/README.md +0 -0
  49. {task_agent-0.1.232 → task_agent-0.1.246}/docs/tasks/completed/2026/create-done-process.md +0 -0
  50. {task_agent-0.1.232 → task_agent-0.1.246}/docs/tasks/completed/2026/create-make-next-script.md +0 -0
  51. {task_agent-0.1.232 → task_agent-0.1.246}/docs/tasks/completed/2026/create-model-configuration-schema/README.md +0 -0
  52. {task_agent-0.1.232 → task_agent-0.1.246}/docs/tasks/completed/2026/create-plugin-sidecar-strategy.md +0 -0
  53. {task_agent-0.1.232 → task_agent-0.1.246}/docs/tasks/completed/2026/define-task-metadata-standard/README.md +0 -0
  54. {task_agent-0.1.232 → task_agent-0.1.246}/docs/tasks/completed/2026/dependent-task.md +0 -0
  55. {task_agent-0.1.232 → task_agent-0.1.246}/docs/tasks/completed/2026/directory-task/README.md +0 -0
  56. {task_agent-0.1.232 → task_agent-0.1.246}/docs/tasks/completed/2026/done-should-require-explainaition-of-solution.md +0 -0
  57. {task_agent-0.1.232 → task_agent-0.1.246}/docs/tasks/completed/2026/dummy-task.md +0 -0
  58. {task_agent-0.1.232 → task_agent-0.1.246}/docs/tasks/completed/2026/enhanced-task-lifecycle-and-git-orchestration/README.md +0 -0
  59. {task_agent-0.1.232 → task_agent-0.1.246}/docs/tasks/completed/2026/fix-uv-lock.md +0 -0
  60. {task_agent-0.1.232 → task_agent-0.1.246}/docs/tasks/completed/2026/history-feature-should-be-sorted-newest-at-the-top/README.md +0 -0
  61. {task_agent-0.1.232 → task_agent-0.1.246}/docs/tasks/completed/2026/history-should-allow-l-to-view/README.md +0 -0
  62. {task_agent-0.1.232 → task_agent-0.1.246}/docs/tasks/completed/2026/history-should-show-datetime-in-iso-with-local-timezone/README.md +0 -0
  63. {task_agent-0.1.232 → task_agent-0.1.246}/docs/tasks/completed/2026/i-can-only-see-20-items-in-the-hsitory-view/README.md +0 -0
  64. {task_agent-0.1.232 → task_agent-0.1.246}/docs/tasks/completed/2026/implement-adk-sidecar-worker-with-multi-agent-architecture.md +0 -0
  65. {task_agent-0.1.232 → task_agent-0.1.246}/docs/tasks/completed/2026/implement-plugin-based-secret-manager/README.md +0 -0
  66. {task_agent-0.1.232 → task_agent-0.1.246}/docs/tasks/completed/2026/implement-strict-worktree-lifecycle/README.md +0 -0
  67. {task_agent-0.1.232 → task_agent-0.1.246}/docs/tasks/completed/2026/implement-ta-done-git-primitive-integration/README.md +0 -0
  68. {task_agent-0.1.232 → task_agent-0.1.246}/docs/tasks/completed/2026/init-mcp-doesnt-work-for-opencode/README.md +0 -0
  69. {task_agent-0.1.232 → task_agent-0.1.246}/docs/tasks/completed/2026/initial-setup.md +0 -0
  70. {task_agent-0.1.232 → task_agent-0.1.246}/docs/tasks/completed/2026/make-nvim-the-default-editor.md +0 -0
  71. {task_agent-0.1.232 → task_agent-0.1.246}/docs/tasks/completed/2026/make-sure-x1e-is-not-used-as-a-record-separator-anywhere.md +0 -0
  72. {task_agent-0.1.232 → task_agent-0.1.246}/docs/tasks/completed/2026/make-ta-version-show-the-latest-pypiorg-version-of-task-agent/README.md +0 -0
  73. {task_agent-0.1.232 → task_agent-0.1.246}/docs/tasks/completed/2026/mcp-init-breaks-in-powershell.md +0 -0
  74. {task_agent-0.1.232 → task_agent-0.1.246}/docs/tasks/completed/2026/migrate-docsissues-to-docstasks.md +0 -0
  75. {task_agent-0.1.232 → task_agent-0.1.246}/docs/tasks/completed/2026/migrate-from-markdown-files-to-folders-with-readmemd/README.md +0 -0
  76. {task_agent-0.1.232 → task_agent-0.1.246}/docs/tasks/completed/2026/migrate-to-tasks-directory.md +0 -0
  77. {task_agent-0.1.232 → task_agent-0.1.246}/docs/tasks/completed/2026/nest-dependent-tasks-under-their-parent-in-list-view.md +0 -0
  78. {task_agent-0.1.232 → task_agent-0.1.246}/docs/tasks/completed/2026/new-issue-feature-in-mcp-should-require-completion-criteria.md +0 -0
  79. {task_agent-0.1.232 → task_agent-0.1.246}/docs/tasks/completed/2026/refactor-models.md +0 -0
  80. {task_agent-0.1.232 → task_agent-0.1.246}/docs/tasks/completed/2026/remove-from-usv-writer-end-of-line.md +0 -0
  81. {task_agent-0.1.232 → task_agent-0.1.246}/docs/tasks/completed/2026/remove-test-issues-from-the-data.md +0 -0
  82. {task_agent-0.1.232 → task_agent-0.1.246}/docs/tasks/completed/2026/rename-triage.md +0 -0
  83. {task_agent-0.1.232 → task_agent-0.1.246}/docs/tasks/completed/2026/restyle-to-not-use-borders/README.md +0 -0
  84. {task_agent-0.1.232 → task_agent-0.1.246}/docs/tasks/completed/2026/secure-secret-management-and-config-abstraction/README.md +0 -0
  85. {task_agent-0.1.232 → task_agent-0.1.246}/docs/tasks/completed/2026/shortcuts-in-prior-are-not-highlighted.md +0 -0
  86. {task_agent-0.1.232 → task_agent-0.1.246}/docs/tasks/completed/2026/sub-agent-observability-and-transparency/README.md +0 -0
  87. {task_agent-0.1.232 → task_agent-0.1.246}/docs/tasks/completed/2026/task-agent-should-not-create-read-only-access-on-each-task-markdown.md +0 -0
  88. {task_agent-0.1.232 → task_agent-0.1.246}/docs/tasks/completed/2026/test-plan.md +0 -0
  89. {task_agent-0.1.232 → task_agent-0.1.246}/docs/tasks/completed/2026/test-task.md +0 -0
  90. {task_agent-0.1.232 → task_agent-0.1.246}/docs/tasks/completed/2026/the-border-around-triage-is-dark-blue-on-black.md +0 -0
  91. {task_agent-0.1.232 → task_agent-0.1.246}/docs/tasks/completed/2026/triage-should-provide-an-add-feature.md +0 -0
  92. {task_agent-0.1.232 → task_agent-0.1.246}/docs/tasks/completed/2026/update-development-standards-to-use-ta-done.md +0 -0
  93. {task_agent-0.1.232 → task_agent-0.1.246}/docs/tasks/completed/2026/upgrade-to-tasks-should-handle-existing-issues-and-tasks.md +0 -0
  94. /task_agent-0.1.232/src/taskagent/models/__init__.py → /task_agent-0.1.246/docs/tasks/mission.usv +0 -0
  95. {task_agent-0.1.232 → task_agent-0.1.246}/docs/workflow/README.md +0 -0
  96. {task_agent-0.1.232 → task_agent-0.1.246}/opencode +0 -0
  97. {task_agent-0.1.232 → task_agent-0.1.246}/sidecars/adk-worker/.env.example +0 -0
  98. {task_agent-0.1.232 → task_agent-0.1.246}/sidecars/adk-worker/pyproject.toml +0 -0
  99. {task_agent-0.1.232 → task_agent-0.1.246}/sidecars/adk-worker/worker.py +0 -0
  100. {task_agent-0.1.232 → task_agent-0.1.246}/src/taskagent/__init__.py +0 -0
  101. {task_agent-0.1.232 → task_agent-0.1.246}/src/taskagent/__main__.py +0 -0
  102. {task_agent-0.1.232 → task_agent-0.1.246}/src/taskagent/config.py +0 -0
  103. {task_agent-0.1.232 → task_agent-0.1.246}/src/taskagent/discovery.py +0 -0
  104. {task_agent-0.1.232 → task_agent-0.1.246}/src/taskagent/mcp.py +0 -0
  105. {task_agent-0.1.232 → task_agent-0.1.246}/src/taskagent/models/issue.py +0 -0
  106. {task_agent-0.1.232 → task_agent-0.1.246}/test-dir/opencode.json +0 -0
  107. {task_agent-0.1.232 → task_agent-0.1.246}/tests/test_cli.py +0 -0
  108. {task_agent-0.1.232 → task_agent-0.1.246}/tests/test_cli_windows.py +0 -0
  109. {task_agent-0.1.232 → task_agent-0.1.246}/tests/test_discovery.py +0 -0
  110. {task_agent-0.1.232 → task_agent-0.1.246}/tests/test_manager.py +0 -0
  111. {task_agent-0.1.232 → task_agent-0.1.246}/tests/test_manager_git.py +0 -0
  112. {task_agent-0.1.232 → task_agent-0.1.246}/tests/test_mcp.py +0 -0
  113. {task_agent-0.1.232 → task_agent-0.1.246}/tests/test_migration.py +0 -0
@@ -15,7 +15,7 @@ test: lint ## Run unit tests
15
15
  @uv run pytest
16
16
 
17
17
  lint: ## Run linting and type checks
18
- @uv run ruff check .
18
+ @uv run ruff check . --fix
19
19
  @uv run ruff format --check .
20
20
  @uv run mypy src
21
21
 
@@ -1,10 +1,11 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: task-agent
3
- Version: 0.1.232
3
+ Version: 0.1.246
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
7
7
  Requires-Python: >=3.12
8
+ Requires-Dist: githubkit>=0.15.5
8
9
  Requires-Dist: mcp>=1.26.0
9
10
  Requires-Dist: pydantic>=2.10.6
10
11
  Requires-Dist: pyperclip>=1.9.0
@@ -0,0 +1,6 @@
1
+ # Add commit and push
2
+
3
+ Add a ta commit to commit and push all current changes in the tasks/
4
+
5
+ ---
6
+ **Completed in commit:** `<pending-commit-id>`
@@ -0,0 +1,6 @@
1
+ # Add configuration file support for GitHub plugin
2
+
3
+ Allow storing GitHub token and repo in .ta-config.json or worktree-config.json. Add validation for required fields.
4
+
5
+ ---
6
+ **Completed in commit:** `a825289`
@@ -0,0 +1,16 @@
1
+ # Integreate with GitHub Issues
2
+
3
+ Allow the user to turn on GitHub Issues integration.
4
+
5
+ This should be a pluggin.
6
+
7
+ We might later want to integrate with Jira or Rally or other systems.
8
+
9
+ We should define a data schema for our current task item structure, probably in Pydantic.
10
+
11
+ We should define another one for the GitHub Issue Schema and their board structure.
12
+
13
+ Then, we should have a clear, declared formal structure to map one to the other. I don't know what the best mapping library or tooling is in Python, but we should think that through and use the most production-ready and robust tooling. Maybe Protocols would be involved.
14
+
15
+ ---
16
+ **Completed in commit:** `<pending-commit-id>`
@@ -1,3 +1,6 @@
1
1
  # Make copy-slug available from the History list
2
2
 
3
3
  Add the keymap to copy-slug to history, so the user can copy the item slug to the clipboard
4
+
5
+ ---
6
+ **Completed in commit:** `<pending-commit-id>`
@@ -49,3 +49,6 @@ No issues match pattern 'systemic-field'.
49
49
  10:56:31 turboship  uat [1998?] is 📦 v0.1.0 via  v24.15.0 on ☁️ bizkite-support (us-east-1)
50
50
 
51
51
  ---
52
+
53
+ ---
54
+ **Completed in commit:** `<pending-commit-id>`
@@ -0,0 +1,12 @@
1
+ # search should include completed items in the resultes
2
+
3
+ Currently, search does not appear to include completed items.
4
+
5
+ Search should be a fuzzy search that matches the first part of the slug, even if the user didn't include dashes, and without case sensitifity, and without trying to match punctuation. Punctuation should be ignored in the search to include more results.
6
+
7
+ ## Solution
8
+
9
+ Updated search to always include completed items and use fuzzy matching that strips dashes/punctuation and is case-insensitive.
10
+
11
+ ---
12
+ **Completed in commit:** `a5e9f5b`
@@ -0,0 +1,10 @@
1
+ # ta list should use the same style template as ta prior
2
+
3
+
4
+
5
+ ## Solution
6
+
7
+ Updated ta list table to match triage style: box=None, padding=(0,2), UPPERCASE status, and simplified columns to Priority, Status, Slug.
8
+
9
+ ---
10
+ **Completed in commit:** `4a91bd4`
@@ -1,6 +1,6 @@
1
1
  [project]
2
2
  name = "task-agent"
3
- version = "0.1.232"
3
+ version = "0.1.246"
4
4
  description = "A prioritized, file-based task queue for autonomous agentic workers"
5
5
  readme = "README.md"
6
6
  requires-python = ">=3.12"
@@ -15,6 +15,7 @@ dependencies = [
15
15
  "pyperclip>=1.9.0",
16
16
  "questionary>=2.1.1",
17
17
  "rich>=13.9.4",
18
+ "githubkit>=0.15.5",
18
19
  ]
19
20
 
20
21
  [project.scripts]
@@ -38,7 +39,7 @@ dev = [
38
39
  ]
39
40
 
40
41
  [tool.bumpversion]
41
- current_version = "0.1.232"
42
+ current_version = "0.1.246"
42
43
  parse = "(?P<major>\\d+)\\.(?P<minor>\\d+)\\.(?P<patch>\\d+)"
43
44
  serialize = ["{major}.{minor}.{patch}"]
44
45
  search = "version = \"{current_version}\""
@@ -27,12 +27,13 @@ try:
27
27
  HAS_TERMIOS = True
28
28
  except ImportError:
29
29
  HAS_TERMIOS = False
30
- try:
31
- import msvcrt
32
30
 
33
- HAS_MSVCRT = True
34
- except ImportError:
35
- HAS_MSVCRT = False
31
+ try:
32
+ import msvcrt
33
+
34
+ HAS_MSVCRT = True
35
+ except ImportError:
36
+ HAS_MSVCRT = False
36
37
 
37
38
  from rich.live import Live
38
39
 
@@ -308,13 +309,57 @@ def cmd_next(console: Console, manager: TaskAgent):
308
309
 
309
310
 
310
311
  def cmd_search(console: Console, manager: TaskAgent, pattern: str):
311
- """Search for issues by slug prefix pattern."""
312
- issues = manager.load_mission()
313
- if not issues:
314
- console.print("[yellow]No issues found.[/yellow]")
312
+ """Search for issues by slug pattern (case-insensitive fuzzy match)."""
313
+ import re
314
+
315
+ # Normalize pattern: remove dashes, punctuation, lowercase
316
+ def normalize(s: str) -> str:
317
+ return re.sub(r"[^a-zA-Z0-9]", "", s).lower()
318
+
319
+ def fuzzy_match(slug: str, pattern: str) -> bool:
320
+ slug_clean = normalize(slug)
321
+ pat_clean = normalize(pattern)
322
+ return pat_clean in slug_clean or slug_clean.startswith(pat_clean)
323
+
324
+ pat_norm = normalize(pattern)
325
+ if not pat_norm:
326
+ console.print("[yellow]No pattern provided.[/yellow]")
315
327
  return
316
328
 
317
- matches = [i for i in issues if i.slug.startswith(pattern)]
329
+ matches: List[Issue] = []
330
+
331
+ # Search mission issues
332
+ issues = manager.load_mission()
333
+ for i in issues:
334
+ if fuzzy_match(i.slug, pat_norm):
335
+ matches.append(i)
336
+
337
+ # Always search completed tasks too
338
+ completed_root = manager.issues_root / "completed"
339
+ if completed_root.exists():
340
+ for year_dir in sorted(completed_root.iterdir(), reverse=True):
341
+ if year_dir.is_dir():
342
+ for f in year_dir.glob("*.md"):
343
+ if fuzzy_match(f.stem, pat_norm):
344
+ name = manager.extract_title(f)
345
+ matches.append(
346
+ Issue(
347
+ name=name, slug=f.stem, status="completed", priority=0
348
+ )
349
+ )
350
+ for d in year_dir.iterdir():
351
+ if d.is_dir():
352
+ readme = d / "README.md"
353
+ if readme.exists() and fuzzy_match(d.name, pat_norm):
354
+ name = manager.extract_title(readme)
355
+ matches.append(
356
+ Issue(
357
+ name=name,
358
+ slug=d.name,
359
+ status="completed",
360
+ priority=0,
361
+ )
362
+ )
318
363
 
319
364
  if not matches:
320
365
  console.print(f"[yellow]No issues match pattern '{pattern}'.[/yellow]")
@@ -401,7 +446,49 @@ def cmd_search(console: Console, manager: TaskAgent, pattern: str):
401
446
  subprocess.run([editor, str(issue_file)])
402
447
  manager.init_project()
403
448
  issues = manager.load_mission()
404
- matches = [i for i in issues if i.slug.startswith(pattern)]
449
+ matches = [i for i in issues if fuzzy_match(i.slug, pat_norm)]
450
+ # Also re-search completed
451
+ completed_root = manager.issues_root / "completed"
452
+ new_matches: List[Issue] = list(matches)
453
+ if completed_root.exists():
454
+ for year_dir in sorted(
455
+ completed_root.iterdir(), reverse=True
456
+ ):
457
+ if year_dir.is_dir():
458
+ for f in year_dir.glob("*.md"):
459
+ if fuzzy_match(f.stem, pat_norm) and not any(
460
+ m.slug == f.stem for m in new_matches
461
+ ):
462
+ name = manager.extract_title(f)
463
+ new_matches.append(
464
+ Issue(
465
+ name=name,
466
+ slug=f.stem,
467
+ status="completed",
468
+ priority=0,
469
+ )
470
+ )
471
+ for d in year_dir.iterdir():
472
+ if d.is_dir():
473
+ readme = d / "README.md"
474
+ if (
475
+ readme.exists()
476
+ and fuzzy_match(d.name, pat_norm)
477
+ and not any(
478
+ m.slug == d.name
479
+ for m in new_matches
480
+ )
481
+ ):
482
+ name = manager.extract_title(readme)
483
+ new_matches.append(
484
+ Issue(
485
+ name=name,
486
+ slug=d.name,
487
+ status="completed",
488
+ priority=0,
489
+ )
490
+ )
491
+ matches = new_matches
405
492
  if cursor >= len(matches):
406
493
  cursor = max(0, len(matches) - 1)
407
494
  else:
@@ -489,7 +576,7 @@ def cmd_history(console: Console, manager: TaskAgent, limit: int = 20):
489
576
  str(absolute_idx + 1), date_str, f"{prefix}{slug}", style=style
490
577
  )
491
578
 
492
- help_text = "[dim]v/l: view | q: exit[/dim]"
579
+ help_text = "[dim]v/l: view | c: copy slug | q: exit[/dim]"
493
580
 
494
581
  live.update(Panel(table, subtitle=help_text, box=box.MINIMAL), refresh=True)
495
582
 
@@ -503,7 +590,7 @@ def cmd_history(console: Console, manager: TaskAgent, limit: int = 20):
503
590
  elif key in ["k", "\x1b[A"]:
504
591
  cursor = max(0, cursor - 1)
505
592
  elif key in ["j", "\x1b[B"]:
506
- cursor = min(len(all_completed) - 1, cursor + 1)
593
+ cursor = min(len(all_completed), cursor + 1)
507
594
  elif key in ["v", "l"]:
508
595
  live.stop()
509
596
  file, slug = all_completed[cursor]
@@ -514,6 +601,14 @@ def cmd_history(console: Console, manager: TaskAgent, limit: int = 20):
514
601
  except Exception:
515
602
  pass
516
603
  live.start()
604
+ elif key in ["c"]:
605
+ # Copy slug to clipboard
606
+ _, slug = all_completed[cursor]
607
+ try:
608
+ pyperclip.copy(slug)
609
+ console.print(f"[green]Copied slug to clipboard: {slug}[/green]")
610
+ except Exception as e:
611
+ console.print(f"[yellow]Failed to copy to clipboard: {e}[/yellow]")
517
612
 
518
613
 
519
614
  def cmd_report(console: Console, manager: TaskAgent, slug: str):
@@ -613,6 +708,76 @@ def cmd_push(console: Console, manager: TaskAgent):
613
708
  console.print(f"[red]Failed to push mission repository: {e}[/red]")
614
709
 
615
710
 
711
+ def cmd_commit(
712
+ console: Console,
713
+ manager: TaskAgent,
714
+ message: Optional[str] = None,
715
+ should_push: bool = True,
716
+ ):
717
+ """Commit and optionally push changes in the tasks/ directory."""
718
+ import subprocess
719
+
720
+ # Determine the tasks directory
721
+ tasks_dir = manager.issues_root
722
+ if not tasks_dir or not tasks_dir.exists():
723
+ console.print("[red]Tasks directory not found.[/red]")
724
+ return
725
+
726
+ # Generate default commit message if not provided
727
+ if not message:
728
+ message = f"Update tasks - {datetime.now().strftime('%Y-%m-%d %H:%M')}"
729
+
730
+ console.print(f"[blue]Committing changes in {tasks_dir}...[/blue]")
731
+
732
+ try:
733
+ # Add all changes in tasks directory
734
+ subprocess.run(
735
+ ["git", "add", str(tasks_dir / ".")],
736
+ check=True,
737
+ capture_output=True,
738
+ text=True,
739
+ shell=(os.name == "nt"),
740
+ )
741
+
742
+ # Check if there are changes to commit
743
+ result = subprocess.run(
744
+ ["git", "diff", "--cached", "--quiet"],
745
+ capture_output=True,
746
+ text=True,
747
+ shell=(os.name == "nt"),
748
+ )
749
+
750
+ if result.returncode == 0:
751
+ console.print("[yellow]No changes to commit in tasks/ directory.[/yellow]")
752
+ return
753
+
754
+ # Commit
755
+ subprocess.run(
756
+ ["git", "commit", "-m", message],
757
+ check=True,
758
+ capture_output=True,
759
+ text=True,
760
+ shell=(os.name == "nt"),
761
+ )
762
+ console.print(f"[bold green]Committed: {message}[/bold green]")
763
+
764
+ # Push if requested
765
+ if should_push:
766
+ if manager.mission_root:
767
+ console.print("[blue]Pushing to remote...[/blue]")
768
+ manager.push_mission_repo()
769
+ console.print("[bold green]Successfully pushed.[/bold green]")
770
+ else:
771
+ console.print(
772
+ "[yellow]No mission repository configured, skipping push.[/yellow]"
773
+ )
774
+
775
+ except subprocess.CalledProcessError as e:
776
+ console.print(f"[red]Error: {e.stderr}[/red]")
777
+ except Exception as e:
778
+ console.print(f"[red]Unexpected error: {e}[/red]")
779
+
780
+
616
781
  def cmd_eject_mission(console: Console, manager: TaskAgent, public: bool = False):
617
782
  """Automate the move of docs/tasks to a separate repository."""
618
783
  source_dir = manager.issues_root
@@ -896,25 +1061,21 @@ def cmd_list(
896
1061
  )
897
1062
  return
898
1063
 
899
- table = Table(title="Task Queue")
1064
+ table = Table(title="Task Queue", box=None, padding=(0, 2))
900
1065
  table.add_column("Priority", justify="right", style="cyan")
901
- table.add_column("Status", style="magenta")
902
- table.add_column("Name", style="white")
903
- table.add_column("Slug", style="green")
904
- table.add_column("Depends On", style="yellow")
905
- table.add_column("Location", style="dim")
1066
+ table.add_column("Status", width=10)
1067
+ table.add_column("Slug")
906
1068
 
907
1069
  for issue, depth in rows_to_display:
908
- issue_file = manager.find_issue_file(issue.slug)
909
- location = str(issue_file) if issue_file else "[red]MISSING[/red]"
910
-
911
- status_str = issue.status
912
- if status_str == "pending":
913
- status_str = f"[bold yellow]{status_str}[/bold yellow]"
914
- elif status_str == "draft":
915
- status_str = f"[dim]{status_str}[/dim]"
916
- elif status_str == "active":
917
- status_str = f"[bold green]{status_str}[/bold green]"
1070
+ status_style = "white"
1071
+ if issue.status == "active":
1072
+ status_style = "bold green"
1073
+ elif issue.status == "pending":
1074
+ status_style = "bold yellow"
1075
+ elif issue.status == "draft":
1076
+ status_style = "dim"
1077
+ elif issue.status == "completed":
1078
+ status_style = "bold blue"
918
1079
 
919
1080
  indent = " " * depth
920
1081
  prefix = "└─ " if depth > 0 else ""
@@ -922,11 +1083,8 @@ def cmd_list(
922
1083
 
923
1084
  table.add_row(
924
1085
  str(issue.priority),
925
- status_str,
926
- issue.name,
1086
+ f"[{status_style}]{issue.status.upper()}[/{status_style}]",
927
1087
  display_slug,
928
- ", ".join(issue.dependencies),
929
- location,
930
1088
  )
931
1089
  console.print(table)
932
1090
 
@@ -1544,6 +1702,86 @@ def cmd_worktree(console: Console, manager: TaskAgent, args):
1544
1702
  console.print(f"[red]Error pruning worktrees: {e.stderr}[/red]")
1545
1703
 
1546
1704
 
1705
+ def cmd_github(console: Console, manager: TaskAgent, args):
1706
+ """Sync with GitHub Issues."""
1707
+ try:
1708
+ from taskagent.plugins.github import GitHubPlugin
1709
+ except ImportError:
1710
+ console.print("[red]GitHub plugin not installed. Run: uv add githubkit[/red]")
1711
+ return
1712
+
1713
+ # Load config from .task-agent/worktree-config.json
1714
+ config = {}
1715
+ config_file = Path(".task-agent/worktree-config.json")
1716
+ if config_file.exists():
1717
+ try:
1718
+ with config_file.open("r", encoding="utf-8") as f:
1719
+ config = json.load(f)
1720
+ except Exception:
1721
+ pass
1722
+
1723
+ # Override repo if specified in args
1724
+ if hasattr(args, "repo") and args.repo:
1725
+ if "github" not in config:
1726
+ config["github"] = {}
1727
+ config["github"]["repo"] = args.repo
1728
+
1729
+ try:
1730
+ plugin = GitHubPlugin(config)
1731
+ except ValueError as e:
1732
+ console.print(f"[red]{e}[/red]")
1733
+ return
1734
+
1735
+ if args.github_command == "sync":
1736
+ try:
1737
+ issues = plugin.sync_from_github()
1738
+ console.print(f"[green]Imported {len(issues)} issues from GitHub[/green]")
1739
+
1740
+ # Add issues to task-agent
1741
+ for issue in issues:
1742
+ try:
1743
+ manager.create_issue(issue.name, body="Imported from GitHub")
1744
+ console.print(f" Added: {issue.name}")
1745
+ except Exception as e:
1746
+ console.print(f" [yellow]Skipped {issue.slug}: {e}[/yellow]")
1747
+
1748
+ # Save mission
1749
+ manager.save_mission(manager.load_mission())
1750
+ console.print("[green]Mission file updated[/green]")
1751
+ except Exception as e:
1752
+ console.print(f"[red]Error syncing: {e}[/red]")
1753
+
1754
+ elif args.github_command == "create":
1755
+ try:
1756
+ issue = None
1757
+ # Find the issue by slug
1758
+ issue_file = manager.find_issue_file(args.slug)
1759
+ if not issue_file:
1760
+ console.print(f"[red]Issue '{args.slug}' not found[/red]")
1761
+ return
1762
+
1763
+ # Load issue details
1764
+ content = (
1765
+ issue_file.read_text()
1766
+ if issue_file.name == "README.md"
1767
+ else issue_file.read_text()
1768
+ )
1769
+ name = content.split("\n")[0].lstrip("#").strip()
1770
+
1771
+ # Create GitHub issue
1772
+ from taskagent.models.issue import Issue
1773
+
1774
+ temp_issue = Issue(name=name, slug=args.slug, dependencies=[])
1775
+ result = plugin.create_github_issue(temp_issue)
1776
+
1777
+ console.print(f"[green]Created GitHub Issue #{result['number']}[/green]")
1778
+ console.print(f"URL: {result['url']}")
1779
+ except Exception as e:
1780
+ console.print(f"[red]Error creating issue: {e}[/red]")
1781
+ else:
1782
+ console.print("[yellow]Use 'sync' or 'create' subcommand[/yellow]")
1783
+
1784
+
1547
1785
  def _copy_files_to_worktree(console: Console, worktree_path: Path, patterns: list):
1548
1786
  """Copy files matching patterns to the worktree directory."""
1549
1787
  import shutil
@@ -1669,51 +1907,6 @@ def _configure_git_user_for_worktree(
1669
1907
  console.print(
1670
1908
  f"[yellow]Warning: Failed to configure git user for worktree: {e}[/yellow]"
1671
1909
  )
1672
- user_email_result = subprocess.run(
1673
- ["git", "config", "--get", "user.email"],
1674
- capture_output=True,
1675
- text=True,
1676
- check=False,
1677
- )
1678
-
1679
- user_name = (
1680
- user_name_result.stdout.strip() if user_name_result.returncode == 0 else ""
1681
- )
1682
- user_email = (
1683
- user_email_result.stdout.strip()
1684
- if user_email_result.returncode == 0
1685
- else ""
1686
- )
1687
-
1688
- # TODO: Implement branch-specific git config logic here
1689
- # For now, we'll just use the current user's config
1690
- # In the future, this could read from a config file to determine
1691
- # which GitHub account to use for which branch
1692
-
1693
- if user_name and user_email:
1694
- # Set git config locally for this worktree
1695
- subprocess.run(
1696
- ["git", "-C", str(worktree_path), "config", "user.name", user_name],
1697
- check=False,
1698
- capture_output=True,
1699
- )
1700
- subprocess.run(
1701
- ["git", "-C", str(worktree_path), "config", "user.email", user_email],
1702
- check=False,
1703
- capture_output=True,
1704
- )
1705
- console.print(
1706
- f"[dim]Configured git user for worktree: {user_name} <{user_email}>[/dim]"
1707
- )
1708
- else:
1709
- console.print(
1710
- "[yellow]Warning: Could not determine git user from current config[/yellow]"
1711
- )
1712
-
1713
- except Exception as e:
1714
- console.print(
1715
- f"[yellow]Warning: Failed to configure git user for worktree: {e}[/yellow]"
1716
- )
1717
1910
 
1718
1911
 
1719
1912
  def cmd_restore(
@@ -2178,6 +2371,21 @@ def main():
2178
2371
  worktree_parser.add_argument(
2179
2372
  "--no-env", action="store_true", help="Do not copy .env files to worktree"
2180
2373
  )
2374
+
2375
+ # GitHub integration
2376
+ github_parser = subparsers.add_parser("github", help="Sync with GitHub Issues")
2377
+ github_sub = github_parser.add_subparsers(dest="github_command")
2378
+
2379
+ sync_parser = github_sub.add_parser("sync", help="Import issues from GitHub")
2380
+ sync_parser.add_argument("--repo", help="Repository (owner/repo) override")
2381
+
2382
+ create_parser = github_sub.add_parser(
2383
+ "create", help="Create GitHub issue from task"
2384
+ )
2385
+ create_parser.add_argument("slug", help="Task slug to create GitHub issue for")
2386
+
2387
+ # Add other commands as needed
2388
+
2181
2389
  up_parser = subparsers.add_parser("up")
2182
2390
  up_parser.add_argument("slug")
2183
2391
  down_parser = subparsers.add_parser("down")
@@ -2221,6 +2429,21 @@ def main():
2221
2429
  # push
2222
2430
  subparsers.add_parser("push", help="Push the mission repository to origin")
2223
2431
 
2432
+ # commit - commit and push changes in tasks/ directory
2433
+ commit_parser = subparsers.add_parser(
2434
+ "commit", help="Commit and push changes in tasks/ directory"
2435
+ )
2436
+ commit_parser.add_argument(
2437
+ "-m", "--message", help="Commit message (default: auto-generated)"
2438
+ )
2439
+ commit_parser.add_argument(
2440
+ "--no-push",
2441
+ dest="push",
2442
+ action="store_false",
2443
+ help="Do not push to remote",
2444
+ )
2445
+ commit_parser.set_defaults(push=True)
2446
+
2224
2447
  # eject-mission
2225
2448
  eject_parser = subparsers.add_parser(
2226
2449
  "eject-mission", help="Move mission queue to a separate repository"
@@ -2362,6 +2585,8 @@ def main():
2362
2585
  )
2363
2586
  elif args.command == "worktree":
2364
2587
  cmd_worktree(console, manager, args)
2588
+ elif args.command == "github":
2589
+ cmd_github(console, manager, args)
2365
2590
  elif args.command == "version":
2366
2591
  if args.version_command == "promote":
2367
2592
  cmd_version(console, promote=args.part, push=False)
@@ -63,20 +63,21 @@ class TaskAgent:
63
63
  return
64
64
 
65
65
  # First handle chattr (immutable attribute)
66
- try:
67
- if writable:
68
- # Remove immutable attribute
66
+ if writable:
67
+ # Remove immutable attribute before making writable
68
+ try:
69
69
  subprocess.run(
70
70
  ["chattr", "-i", str(path)],
71
71
  capture_output=True,
72
- check=False,
72
+ check=True,
73
73
  shell=(os.name == "nt"),
74
74
  )
75
- else:
76
- # Set immutable attribute (after chmod)
77
- pass # We'll set it after chmod
78
- except Exception:
79
- pass
75
+ except subprocess.CalledProcessError:
76
+ raise RuntimeError(
77
+ f"Cannot modify {path.name} (immutable attribute set).\n"
78
+ f"Run: sudo chattr -i {path}\n"
79
+ f"Or: sudo setcap cap_linux_immutable+ep $(which ta)"
80
+ )
80
81
 
81
82
  # Handle chmod
82
83
  current_mode = path.stat().st_mode
File without changes
@@ -0,0 +1,22 @@
1
+ """Plugin system for task-agent external integrations."""
2
+
3
+ from typing import Protocol, List, Optional
4
+ from taskagent.models.issue import Issue
5
+
6
+
7
+ class IssueProvider(Protocol):
8
+ """Protocol for external issue tracking system providers."""
9
+
10
+ def __init__(self, config: dict): ...
11
+
12
+ def sync_from_external(self) -> List[Issue]:
13
+ """Import issues from external system."""
14
+ ...
15
+
16
+ def sync_to_external(self, issues: List[Issue]) -> None:
17
+ """Export issues to external system."""
18
+ ...
19
+
20
+ def get_external_issue(self, issue_id: str) -> Optional[Issue]:
21
+ """Get a specific issue by ID."""
22
+ ...