diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index bf9f87812a..d5b4daa61c 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -6,7 +6,13 @@ repos: entry: python3 agentydragon/tools/check_task_frontmatter.py language: python additional_dependencies: [PyYAML, toml, pydantic] - files: ^agentydragon/tasks/[0-9]{2}-.*\.md$ + files: ^agentydragon/tasks/(?:\.done/)?[0-9]{2}-.*\.md$ + - id: check-task-dependency-cycles + name: Check for circular task dependencies + entry: python3 agentydragon/tools/check_task_cycles.py + language: python + additional_dependencies: [toml, pydantic] + files: ^agentydragon/tasks/(?:\.done/)?[0-9]{2}-.*\.md$ - id: cargo-build name: Check Rust workspace builds entry: bash -lc 'cd codex-rs && RUSTFLAGS="-D warnings" cargo build --workspace --locked' diff --git a/agentydragon/tasks/.done/.gitkeep b/agentydragon/tasks/.done/.gitkeep new file mode 100644 index 0000000000..e755ce8b4f --- /dev/null +++ b/agentydragon/tasks/.done/.gitkeep @@ -0,0 +1 @@ + # Keep this directory in version control diff --git a/agentydragon/tasks/01-dynamic-mount-commands.md b/agentydragon/tasks/.done/01-dynamic-mount-commands.md similarity index 100% rename from agentydragon/tasks/01-dynamic-mount-commands.md rename to agentydragon/tasks/.done/01-dynamic-mount-commands.md diff --git a/agentydragon/tasks/03-live-config-reload.md b/agentydragon/tasks/.done/03-live-config-reload.md similarity index 100% rename from agentydragon/tasks/03-live-config-reload.md rename to agentydragon/tasks/.done/03-live-config-reload.md diff --git a/agentydragon/tasks/06-external-editor-prompt.md b/agentydragon/tasks/.done/06-external-editor-prompt.md similarity index 100% rename from agentydragon/tasks/06-external-editor-prompt.md rename to agentydragon/tasks/.done/06-external-editor-prompt.md diff --git a/agentydragon/tasks/07-undo-feedback-decision.md b/agentydragon/tasks/.done/07-undo-feedback-decision.md similarity index 100% rename from agentydragon/tasks/07-undo-feedback-decision.md rename to agentydragon/tasks/.done/07-undo-feedback-decision.md diff --git a/agentydragon/tasks/08-set-shell-title.md b/agentydragon/tasks/.done/08-set-shell-title.md similarity index 100% rename from agentydragon/tasks/08-set-shell-title.md rename to agentydragon/tasks/.done/08-set-shell-title.md diff --git a/agentydragon/tasks/10-inspect-container-state.md b/agentydragon/tasks/.done/10-inspect-container-state.md similarity index 100% rename from agentydragon/tasks/10-inspect-container-state.md rename to agentydragon/tasks/.done/10-inspect-container-state.md diff --git a/agentydragon/tasks/11-custom-approval-predicates.md b/agentydragon/tasks/.done/11-custom-approval-predicates.md similarity index 100% rename from agentydragon/tasks/11-custom-approval-predicates.md rename to agentydragon/tasks/.done/11-custom-approval-predicates.md diff --git a/agentydragon/tasks/13-interactive-prompt-during-execution.md b/agentydragon/tasks/.done/13-interactive-prompt-during-execution.md similarity index 100% rename from agentydragon/tasks/13-interactive-prompt-during-execution.md rename to agentydragon/tasks/.done/13-interactive-prompt-during-execution.md diff --git a/agentydragon/tasks/15-agent-worktree-sandbox-configuration.md b/agentydragon/tasks/.done/15-agent-worktree-sandbox-configuration.md similarity index 100% rename from agentydragon/tasks/15-agent-worktree-sandbox-configuration.md rename to agentydragon/tasks/.done/15-agent-worktree-sandbox-configuration.md diff --git a/agentydragon/tasks/16-confirm-on-ctrl-d.md b/agentydragon/tasks/.done/16-confirm-on-ctrl-d.md similarity index 100% rename from agentydragon/tasks/16-confirm-on-ctrl-d.md rename to agentydragon/tasks/.done/16-confirm-on-ctrl-d.md diff --git a/agentydragon/tasks/18-chat-ui-textarea-overlay-border-fix.md b/agentydragon/tasks/.done/18-chat-ui-textarea-overlay-border-fix.md similarity index 100% rename from agentydragon/tasks/18-chat-ui-textarea-overlay-border-fix.md rename to agentydragon/tasks/.done/18-chat-ui-textarea-overlay-border-fix.md diff --git a/agentydragon/tasks/19-bash-command-rendering-improvements.md b/agentydragon/tasks/.done/19-bash-command-rendering-improvements.md similarity index 100% rename from agentydragon/tasks/19-bash-command-rendering-improvements.md rename to agentydragon/tasks/.done/19-bash-command-rendering-improvements.md diff --git a/agentydragon/tasks/21-compact-markdown-rendering.md b/agentydragon/tasks/.done/21-compact-markdown-rendering.md similarity index 100% rename from agentydragon/tasks/21-compact-markdown-rendering.md rename to agentydragon/tasks/.done/21-compact-markdown-rendering.md diff --git a/agentydragon/tasks/28-include-command-snippet-in-approval-label.md b/agentydragon/tasks/.done/28-include-command-snippet-in-approval-label.md similarity index 100% rename from agentydragon/tasks/28-include-command-snippet-in-approval-label.md rename to agentydragon/tasks/.done/28-include-command-snippet-in-approval-label.md diff --git a/agentydragon/tasks/31-display-context-remaining-percentage.md b/agentydragon/tasks/.done/31-display-context-remaining-percentage.md similarity index 100% rename from agentydragon/tasks/31-display-context-remaining-percentage.md rename to agentydragon/tasks/.done/31-display-context-remaining-percentage.md diff --git a/agentydragon/tools/check_task_cycles.py b/agentydragon/tools/check_task_cycles.py new file mode 100755 index 0000000000..7bd4ba6a8a --- /dev/null +++ b/agentydragon/tools/check_task_cycles.py @@ -0,0 +1,51 @@ +#!/usr/bin/env python3 +""" +check_task_cycles.py: Pre-commit hook to detect circular dependencies among non-merged tasks. +""" +import re +import sys + +from manager_utils.tasklib import task_dir, load_task + +def main(): + # Load all tasks and separate merged vs non-merged + merged = set() + deps_map = {} + for md in task_dir().rglob('[0-9][0-9]-*.md'): + if md.name == 'task-template.md' or md.name.endswith('-plan.md'): + continue + meta, _ = load_task(md) + if meta.status == 'Merged': + merged.add(meta.id) + else: + # extract numeric dependencies + deps = [d for d in re.findall(r"\d+", meta.dependencies)] + deps_map[meta.id] = deps + + # filter out dependencies on merged tasks + for tid in deps_map: + deps_map[tid] = [d for d in deps_map[tid] if d not in merged] + + # detect cycles via DFS + visited = set() + stack = [] + + def visit(n): + if n in stack: + cycle = stack[stack.index(n):] + [n] + print(f"Circular dependency detected: {' -> '.join(cycle)}") + sys.exit(1) + if n in visited: + return + stack.append(n) + for m in deps_map.get(n, []): + visit(m) + stack.pop() + visited.add(n) + + for node in deps_map: + if node not in visited: + visit(node) + +if __name__ == '__main__': + main() diff --git a/agentydragon/tools/check_task_frontmatter.py b/agentydragon/tools/check_task_frontmatter.py index 84dd6c3f74..1484b15d19 100755 --- a/agentydragon/tools/check_task_frontmatter.py +++ b/agentydragon/tools/check_task_frontmatter.py @@ -23,12 +23,12 @@ except ImportError: sys.exit(1) REQUIRED_KEYS = ["id", "title", "status", "summary", "goal"] -ALLOWED_STATUSES = ["Not started", "Started", "Needs manual review", "Done", "Cancelled"] +ALLOWED_STATUSES = ["Not started", "Started", "Needs manual review", "Done", "Cancelled", "Merged"] def main(): failures = 0 - for md in tasklib.task_dir().glob('[0-9][0-9]-*.md'): + for md in tasklib.task_dir().rglob('[0-9][0-9]-*.md'): if md.name == 'task-template.md' or md.name.endswith('-plan.md'): continue try: diff --git a/agentydragon/tools/manager_utils/agentydragon_task.py b/agentydragon/tools/manager_utils/agentydragon_task.py index 5147bf5688..a7fece0031 100644 --- a/agentydragon/tools/manager_utils/agentydragon_task.py +++ b/agentydragon/tools/manager_utils/agentydragon_task.py @@ -31,7 +31,7 @@ def status(): # Load all task metadata, reporting load errors with file path all_meta: dict[str, TaskMeta] = {} path_map: dict[str, Path] = {} - for md in sorted(task_dir().glob('*.md')): + for md in sorted(task_dir().rglob('[0-9][0-9]-*.md')): if md.name in ('task-template.md',) or md.name.endswith('-plan.md'): continue try: @@ -133,9 +133,17 @@ def status(): ['git', 'rev-list', '--left-right', '--count', f'{branches[0]}...agentydragon'], cwd=root ).decode().split() - stat = subprocess.check_output( - ['git', 'diff', '--shortstat', f'{branches[0]}...agentydragon'], cwd=root - ).decode().strip().replace(' file changed', '') + # compact diffstat: e.g. "56 files changed, 1265 insertions(+), 342 deletions(-)" -> "56f,1265i,342d" + raw = subprocess.check_output( + ['git', 'diff', '--shortstat', f'{branches[0]}...agentydragon'], cwd=root + ).decode().strip() + stat = ( + raw.replace(' files changed', 'f') + .replace(' file changed', 'f') + .replace(' insertions(+)', 'i') + .replace(' deletions(-)', 'd') + .replace(', ', ',') + ) base = subprocess.check_output( ['git', 'merge-base', 'agentydragon', branches[0]], cwd=root ).decode().strip() @@ -175,7 +183,7 @@ def status(): # summary of fully merged tasks (no branch, no worktree) if merged_tasks: items = ' '.join(f"{tid} ({title})" for tid, title in merged_tasks) - print(f"\n\033[32mDone & merged:\033[0m {items}") + print(f"\n\033[32mMerged:\033[0m {items}") # summary of tasks Ready to merge (Done with branch commits) ready_tasks: list[tuple[str, str]] = [] diff --git a/agentydragon/tools/manager_utils/organize_done_tasks.py b/agentydragon/tools/manager_utils/organize_done_tasks.py new file mode 100755 index 0000000000..b890f3d489 --- /dev/null +++ b/agentydragon/tools/manager_utils/organize_done_tasks.py @@ -0,0 +1,28 @@ +#!/usr/bin/env python3 +""" +organize_done_tasks.py: Move merged task files under tasks/.done/ subdirectory. + +This script should be run once to migrate all tasks with status "Merged" +to the .done folder. +""" +import subprocess +from pathlib import Path + +from tasklib import task_dir, load_task + +def main(): + root = task_dir() + done_dir = root / '.done' + done_dir.mkdir(exist_ok=True) + for md in sorted(root.glob('[0-9][0-9]-*.md')): + if md.name == 'task-template.md' or md.name.endswith('-plan.md'): + continue + meta, _ = load_task(md) + if meta.status == 'Merged': + target = done_dir / md.name + print(f'Moving {md.name} -> .done/') + subprocess.run(['git', 'mv', str(md), str(target)], check=True) + print('Migration complete.') + +if __name__ == '__main__': + main()