From 7bc93f58bb9dc0b5b175bda3e25d32ddeea3fff0 Mon Sep 17 00:00:00 2001 From: ducdmdev Date: Fri, 20 Mar 2026 09:37:34 +0700 Subject: [PATCH 01/22] docs: add DAG-aligned improvements design spec for v2.6.0 MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Three features: Critical Path Identification, Task-Level Resume/Caching, and Early Integration Checkpoints — all sharing a centralized task-graph.json workspace file as their DAG engine. --- ...6-03-20-dag-aligned-improvements-design.md | 533 ++++++++++++++++++ 1 file changed, 533 insertions(+) create mode 100644 docs/specs/2026-03-20-dag-aligned-improvements-design.md diff --git a/docs/specs/2026-03-20-dag-aligned-improvements-design.md b/docs/specs/2026-03-20-dag-aligned-improvements-design.md new file mode 100644 index 0000000..2688aa2 --- /dev/null +++ b/docs/specs/2026-03-20-dag-aligned-improvements-design.md @@ -0,0 +1,533 @@ +# DAG-Aligned Improvements — Design Spec + +**Date**: 2026-03-20 +**Version**: v2.6.0 +**Status**: DRAFT + +## Summary + +Add three DAG-aligned features to the agent-team-plugin: Critical Path Identification, Task-Level Resume/Caching, and Early Integration Checkpoints. All three share a centralized `task-graph.json` workspace file as their core data structure. + +## Motivation + +The agent-team-plugin uses DAG-like task dependencies (blocked-by relationships) but lacks explicit DAG primitives. A deep dive review against industry DAG best practices (workflow orchestration, build systems, CI/CD) identified three high-impact gaps: + +1. **Critical path is implicit** — the lead treats all tasks equally rather than prioritizing the chain that determines total execution time +2. **No task-level caching** — interrupted teams restart from scratch with no way to skip completed work +3. **Integration checks come too late** — diamond dependency conflicts are only caught at the Phase 5 completion gate, after all work is done + +## Design Decisions + +| Decision | Choice | Rationale | +|---|---|---| +| Scope | All 3 features in v2.6.0 | Features share `task-graph.json` — shipping together avoids partial schema | +| Implementation depth | Documentation + scripts + hooks | Full enforcement, consistent with plugin's existing hook-based discipline | +| Hook model | Separate entries per feature | Single responsibility per script, consistent with DAG atomicity principle | +| Dependency data | `task-graph.json` (scripts) + `tasks.md` CP column (humans) | JSON for reliable script computation, markdown for visual awareness | +| Resume behavior | Smart resume with staleness validation | Validates completed work via git timestamps before reuse | +| Integration trigger | Informational nudge | Consistent with TeammateIdle pattern — hooks surface info, lead decides action | + +## Feature 1: Critical Path Identification + +### What It Does + +Computes and displays the longest dependency chain in the task graph. Focuses the lead's attention on tasks that determine total execution time. + +### Where It Appears + +- **Phase 2** — plan presentation shows the critical path and non-critical tasks +- **Phase 3** — `task-graph.json` created with initial critical path computed +- **Phase 4** — `compute-critical-path.sh` hook fires on every TaskCompleted, outputs remaining critical path. Lead prioritizes critical-path blockers. +- **Phase 5** — report includes critical path metrics (initial length, final length, shift count) + +### Critical Path Computation + +Longest-path traversal on the DAG: + +1. **Cycle guard**: Before computing, validate the graph is acyclic — for each node, trace `depends_on` chains and track visited nodes. If any chain revisits a node, the graph has a cycle. Log an error and skip critical path computation (the lead must fix the cycle per the existing Circular Dependency Detection pattern in `coordination-advanced.md`). +2. For each node with no dependents (leaf/sink nodes), trace backward through `depends_on` chains +3. The longest chain (by node count) is the critical path +4. **Tie-breaking**: when multiple chains have the same length, select the chain whose first node has the lowest task ID (lexicographic order). This ensures deterministic output across recomputations. +5. Mark all nodes on this chain with `critical_path: true` +6. Recompute after every status change — the critical path can shift as tasks complete + +### Phase 2 Display + +Add to plan presentation after the Task breakdown: + +``` +Critical path: #1 → #3 → #4 (length: 3) + Non-critical (can slip without affecting total time): #2 + Integration checkpoints: #3 (converges #1 + #2 — verify interface compatibility) +``` + +Add to Phase 2 self-check: + +> "Have I identified the critical path? Is it displayed in the plan? Are convergence points marked?" + +### Phase 4 Prioritization Rules + +- **BLOCKED on critical path** → resolve immediately (highest-priority coordination action) +- **BLOCKED on non-critical path** → resolve normally (slippage has slack) +- **Teammate idle on critical path** → reassign work to keep critical chain moving +- **Teammate idle on non-critical path** → lower priority, consider assigning critical-path support + +### Deadline Escalation Integration + +Critical-path tasks get accelerated escalation: +- Critical-path task stalled → skip Nudge, go directly to Warn +- Non-critical task stalled → follow normal Nudge → Warn → Escalate ladder + +### `tasks.md` CP Column + +All four tables (In Progress, Blocked, Pending, Completed) gain a CP column: + +```markdown +| ID | Subject | Owner | Ref | CP | Notes | +|----|---------|-------|-----|----|-------| +| #1 | Refactor token validation | auth-impl-1 | | ★ | | +| #3 | Update middleware | auth-impl-1 | | ★ | convergence: #1, #2 | +``` + +`★` marks critical path tasks. `convergence: #X, #Y` in Notes indicates convergence points. Lead updates markers when critical path shifts. + +## Feature 2: Task-Level Resume/Caching + +### What It Does + +Detects existing workspaces with incomplete tasks at session start. Validates whether completed work is still fresh by checking git timestamps on output files. Presents the user with a resume-or-start-fresh choice. + +### Resume Detection (`detect-resume.sh`) + +Fires on every SessionStart (no matcher — broader than the existing compact-only `recover-context.sh`): + +1. Scan for `.agent-team/*/task-graph.json` files +2. Skip workspaces where all nodes are `completed` +3. For incomplete workspaces, validate each completed task: + - Read `completed_at` from node + - Check `git log -1 --format=%cI -- ` for each `output_files` entry + - **Valid**: file unchanged since `completed_at` + - **Stale**: file modified after `completed_at` + - **Missing**: file no longer exists +4. Output resume context to stdout (injected into conversation context) with valid/stale/remaining breakdown + +### Resume Output Format + +``` +Resumable workspace found: .agent-team/0319-refactor-auth/ + Tasks: 2/4 completed, 2 remaining + Completed (valid): #1 (Refactor token validation) — output files unchanged + Completed (stale): #2 (Extract session management) — src/auth/session.ts modified after completion + Remaining: #3 (Update middleware), #4 (Review all changes) + Critical path (remaining): #3 → #4 + + To resume: "resume team 0319-refactor-auth" + To start fresh: proceed normally (existing workspace will be archived) +``` + +### Phase 3 Resume Protocol + +New step 1a before TeamCreate: + +If `detect-resume.sh` surfaced a resumable workspace, present options: + +``` +Existing workspace found: .agent-team/{team-name}/ + Completed (valid): {list} + Completed (stale): {list} + Remaining: {list} + +Options: +1. Resume — skip valid completed tasks, re-run stale tasks, continue with remaining +2. Start fresh — archive existing workspace, create new +``` + +**If resuming**: +- Skip TeamCreate if team still exists +- Reuse workspace directory and all tracking files +- Reset stale nodes to `pending` in `task-graph.json` +- Create TaskCreate entries only for remaining + stale tasks +- Spawn teammates for remaining work +- Log in `progress.md` Decision Log: "Resumed from existing workspace. {N} valid, {M} stale, {K} remaining." +- Proceed to Phase 3 step 5 (spawn teammates) + +**If starting fresh**: +- Rename `.agent-team/{team-name}/` to `.agent-team/{team-name}-archived/` +- Proceed with normal Phase 3 + +### Staleness Validation Details + +Edge cases: +- `git` not available → skip staleness check, report all completed tasks as "valid (unverified)" +- Output file doesn't exist in git history → treat as valid (file was newly created by the task) +- `completed_at` is null → task was never completed, treat as remaining +- Multiple incomplete workspaces → list all, most recent first (by `updated` timestamp in `task-graph.json`) + +## Feature 3: Early Integration Checkpoints + +### What It Does + +Detects when two converging streams both complete (diamond pattern) and nudges the lead to verify interface compatibility before the downstream task starts. + +### Convergence Point Detection + +Static analysis at Phase 3 creation time: +- A node is a convergence point when `depends_on` has 2+ entries +- Set `convergence_point: true` — scripts derive the converging tasks from `depends_on` +- Convergence points don't change during execution + +### Integration Checkpoint Hook (`check-integration-point.sh`) + +Fires on TaskCompleted: + +1. Read `task-graph.json` +2. Find all nodes where `convergence_point: true` +3. For each, check if ALL `depends_on` nodes have `status: "completed"` +4. If fully unblocked, output nudge: + +``` +Integration checkpoint reached: Task #3 (Update middleware) + All upstream tasks completed: #1 (auth-impl-1), #2 (auth-impl-2) + These streams produced independent changes that must integrate at #3. + Recommend: verify interface compatibility before #3 starts. + Shared interfaces: check output_files of #1 and #2 for contract alignment. +``` + +5. Skip if convergence point is already `in_progress` or `completed` +6. Silent when no convergence point is fully unblocked + +### Lead Response Protocol + +When the hook fires an integration nudge: + +1. Read `output_files` from both upstream tasks +2. Quick compatibility check — do the outputs define compatible interfaces? +3. **If compatible** → message convergence task owner: "Upstream tasks complete. Interfaces verified. Proceed." +4. **If unclear or incompatible** → message upstream owners + convergence owner: "Integration issue detected." Log in `issues.md` as medium severity. +5. Log in `progress.md` Decision Log: "Integration checkpoint: #Z unblocked by #X + #Y, compatibility [verified|flagged]" + +### Phase 2 Display + +Convergence points shown in plan presentation: + +``` +Integration checkpoints: #3 (converges #1 + #2 — verify interface compatibility) +``` + +### Phase 5 Completion Gate Integration + +For `agent-implement`: Check #4 (Integration) gains awareness — if any convergence points were flagged during Phase 4, verify they were resolved before passing. + +## Shared Data Structure: `task-graph.json` + +### Schema + +```json +{ + "team": "{team-name}", + "created": "{ISO 8601 timestamp}", + "updated": "{ISO 8601 timestamp}", + "nodes": { + "#{id}": { + "subject": "{task subject line}", + "owner": "{teammate-name}", + "status": "pending|in_progress|completed|blocked", + "depends_on": ["#{id}"], + "completed_at": "{ISO 8601 timestamp}|null", + "output_files": ["{relative file paths}"], + "critical_path": true, + "convergence_point": true + } + }, + "critical_path": ["#{id}"], + "critical_path_length": 0 +} +``` + +### Field Reference + +| Field | Type | Description | +|---|---|---| +| `team` | string | Team name matching TeamCreate | +| `created` | ISO timestamp | When the graph was first created | +| `updated` | ISO timestamp | Last modification time | +| `nodes` | object | Map of task ID → node data | +| `nodes.*.subject` | string | Task subject line | +| `nodes.*.owner` | string | Assigned teammate name | +| `nodes.*.status` | enum | `pending`, `in_progress`, `completed`, `blocked` | +| `nodes.*.depends_on` | string[] | Task IDs this node depends on | +| `nodes.*.completed_at` | timestamp/null | When the task was completed (null if not yet) | +| `nodes.*.output_files` | string[] | Relative file paths produced by this task | +| `nodes.*.critical_path` | boolean | Whether this node is on the current critical path | +| `nodes.*.convergence_point` | boolean | Whether this node has 2+ upstream dependencies. Scripts derive converging task IDs from `depends_on` when this is `true` — no separate `converges_from` field needed. | +| `critical_path` | string[] | Ordered list of task IDs forming the current critical path | +| `critical_path_length` | number | Number of nodes on the critical path | + +### Lifecycle + +| Phase | Action | +|---|---| +| Phase 3 step 4a | Create with full graph. Compute initial critical path and convergence points | +| Phase 4 (STARTING) | Update node status to `in_progress` | +| Phase 4 (COMPLETED) | Update node status to `completed`, set `completed_at` and `output_files`. Recompute `critical_path`. **Self-check**: read the file back after editing to verify JSON is valid — malformed JSON silently disables all three hook scripts. | +| Phase 4 (BLOCKED) | Update node status to `blocked` | +| Phase 4 (re-plan) | Rebuild graph from revised task set | +| Phase 5 | Final state preserved as audit artifact | +| Resume | Read by `detect-resume.sh`, stale nodes reset to `pending` | + +### Applicability by Archetype + +| Archetype | Create task-graph.json? | Critical path useful? | Convergence points useful? | Resume useful? | +|---|---|---|---|---| +| Implementation | Yes | Yes — prioritize build chain | Yes — diamond deps on shared interfaces | Yes — code artifacts have output_files | +| Research | Yes | Yes — prioritize blocking research angles | Rare — research streams usually independent | Limited — no output files to validate | +| Audit | Yes | Yes — prioritize blocking audit lenses | Rare — audit lenses usually independent | Limited — no output files to validate | +| Planning | Yes | Yes — prioritize blocking planning concerns | Sometimes — design decisions may converge | Limited — workspace-only outputs | +| Hybrid | Yes | Yes | Yes — mixed streams often converge | Yes — implementation components have output_files | + +Note: For read-only archetypes (Research, Audit, Planning), `output_files` will typically be empty or reference workspace files. Staleness validation in resume mode uses git-tracked files only, so resume is most valuable for Implementation and Hybrid teams. + +## New Scripts + +### `scripts/compute-critical-path.sh` + +**Hook event**: TaskCompleted +**Timeout**: 15s +**Exit code**: Always 0 (informational) + +Behavior: +1. Read hook JSON input. Extract `cwd` (project directory) and `team_name`. If either is empty, exit 0. +2. Resolve workspace path: `${CWD}/.agent-team/${TEAM_NAME}/task-graph.json`. Try `-fix` suffix fallback for remediation teams (matching `verify-task-complete.sh` pattern). +3. Read `task-graph.json` — exit 0 silently if not found or `jq` missing +4. Validate DAG is acyclic (track visited nodes during traversal, break on revisit) +5. Find remaining (non-completed) nodes +6. Compute longest `depends_on` chain via depth-first traversal. Tie-break by lowest task ID. +7. Output critical path status to stderr + +Output when critical-path task completes: +``` +Critical path update: Task #1 completed (was on critical path). +Remaining critical path: #3 → #4 (length: 2) +Next critical task: #3 (owner: auth-impl-1, status: pending, blocked by: #2) +⚠ Critical task #3 is blocked — resolve blocker #2 to maintain throughput. +``` + +Output when non-critical task completes: +``` +Task #2 completed (not on critical path). Critical path unchanged: #1 → #3 → #4 (length: 3) +``` + +Output when no chains remain: +``` +No critical path — all remaining tasks can run in parallel. +``` + +### `scripts/detect-resume.sh` + +**Hook event**: SessionStart (no matcher) +**Timeout**: 15s +**Exit code**: Always 0 (informational) + +Behavior: +1. Read hook JSON input. Extract `cwd` (project directory). Fall back to `.` if empty. Exit 0 if `jq` missing. +2. Scan `${CWD}/.agent-team/*/task-graph.json` — exit 0 silently if none found +3. Filter to incomplete workspaces (any node not completed) +4. For each completed node, validate output files via `git log` timestamps +5. Output resume context to **stdout** (injected into conversation context, matching `recover-context.sh` pattern) + +Staleness check: +- Compare `completed_at` timestamp with `git log -1 --format=%cI -- ` +- File modified after completion → stale +- File missing from git → valid (newly created) +- `git` unavailable → "valid (unverified)" + +### `scripts/check-integration-point.sh` + +**Hook event**: TaskCompleted +**Timeout**: 15s +**Exit code**: Always 0 (informational) + +Behavior: +1. Read hook JSON input. Extract `cwd` (project directory) and `team_name`. If either is empty, exit 0. +2. Resolve workspace path: `${CWD}/.agent-team/${TEAM_NAME}/task-graph.json`. Try `-fix` suffix fallback for remediation teams. +3. Read `task-graph.json` — exit 0 silently if not found or `jq` missing +4. Find convergence points (`convergence_point: true`) +5. Check if all `depends_on` nodes are `completed` +6. If fully unblocked and convergence node is still `pending`, output nudge to stderr +7. Silent when no convergence point is fully unblocked + +## hooks.json Changes + +Add three new entries. Full TaskCompleted and SessionStart sections after changes: + +```json +{ + "TaskCompleted": [ + { + "hooks": [{ "type": "command", "command": "${CLAUDE_PLUGIN_ROOT}/scripts/verify-task-complete.sh", "timeout": 30 }] + }, + { + "hooks": [{ "type": "command", "command": "${CLAUDE_PLUGIN_ROOT}/scripts/compute-critical-path.sh", "timeout": 15 }] + }, + { + "hooks": [{ "type": "command", "command": "${CLAUDE_PLUGIN_ROOT}/scripts/check-integration-point.sh", "timeout": 15 }] + } + ], + "SessionStart": [ + { + "matcher": "compact", + "hooks": [{ "type": "command", "command": "${CLAUDE_PLUGIN_ROOT}/scripts/recover-context.sh", "timeout": 10 }] + }, + { + "hooks": [{ "type": "command", "command": "${CLAUDE_PLUGIN_ROOT}/scripts/detect-resume.sh", "timeout": 15 }] + } + ] +} +``` + +Note: `detect-resume.sh` has no matcher (fires on all session starts). `recover-context.sh` keeps its `compact` matcher. + +## Documentation Changes + +### `docs/shared-phases.md` + +- **Phase 1b**: New step 5a — "Mark convergence points" inserted after existing step 5 ("Integration points — for each pair of streams, identify where plan tasks reference shared interfaces, contracts, or outputs") +- **Phase 2**: Add critical path display and integration checkpoints to plan template. Add self-check item 4 for critical path verification. +- **Phase 3**: New step 1a — resume detection and user choice. New step 4a — create `task-graph.json`. +- **Phase 4**: Add `task-graph.json` update to COMPLETED processing rule (include JSON self-check: read file back after editing to verify valid JSON — malformed JSON silently disables all three hook scripts). New "Critical Path Awareness" subsection. New integration checkpoint processing row in Lead Processing Rules. Add note to scripts: log a specific warning to stderr when `task-graph.json` exists but fails to parse (not just silent exit 0). + +### `docs/workspace-templates.md` + +- New `task-graph.json` section with schema, field reference, and lifecycle +- New rows in Workspace Update Protocol table for task-graph.json events +- Updated `tasks.md` template with CP column in all four tables + +### `docs/coordination-patterns.md` + +- New "Resume from Existing Workspace" section — valid/stale/remaining protocol, archive protocol +- New "Integration Checkpoint Response" section — lead response protocol for convergence nudges + +### `docs/coordination-advanced.md` + +- Updated "Deadline Escalation" — critical-path tasks get accelerated escalation (skip Nudge, go to Warn) + +### `docs/report-format.md` + +- Team Metrics table gains: critical path length, integration checkpoints, resumed tasks +- Task Ledger gains CP column with ★ markers + +### Archetype SKILL.md files (all 5) + +- Phase 3 Override: reference step 4a (create `task-graph.json`) +- `agent-implement`: Completion Gate check #4 gains convergence-point awareness + +### `README.md` + +- Workspace section: add `task-graph.json` to file tree +- Hooks section: add three new hooks with descriptions +- Plugin Structure: add three new scripts + +### `CLAUDE.md` + +- File Ownership table: add `task-graph.json` row +- hooks.json row: update hook entry count from "6 hooks" → "9 hook entries" (6 event types unchanged, 3 new entries on existing events). Note: CLAUDE.md currently says "6 hooks" counting entries — maintain that convention but clarify in parenthetical. +- scripts row: fix baseline from "7 scripts" → "12 scripts" (actual current count is 9 — includes `record-demo.sh` and `generate-demo-cast.sh` — plus 3 new) +- Verify Hooks: add three new verification scenarios + +### `CHANGELOG.md` + +Add v2.6.0 entry with: +- **Added**: `task-graph.json` workspace file (DAG with critical path and convergence points), `compute-critical-path.sh` hook, `detect-resume.sh` hook, `check-integration-point.sh` hook, Critical Path Awareness in Phase 4, Resume from Existing Workspace coordination pattern, Integration Checkpoint Response coordination pattern, CP column in `tasks.md` +- **Changed**: Phase 1b gains convergence point marking, Phase 2 gains critical path display, Phase 3 gains resume detection and `task-graph.json` creation, Phase 4 gains critical-path-weighted prioritization, Deadline Escalation gains critical-path acceleration, report gains critical path metrics + +### `.claude-plugin/plugin.json` and `.claude-plugin/marketplace.json` + +Bump version from `2.5.1` to `2.6.0` in both files (must stay in sync per CLAUDE.md conventions). + +## Tests + +### New Test Files + +**`tests/hooks/test-compute-critical-path.sh`** (~8-9 assertions): +- Exits 0 when no `task-graph.json` exists +- Exits 0 when `jq` is missing +- Correct critical path output when critical-path task completes +- "Not on critical path" output when non-critical task completes +- "No critical path — all parallel" when no chains remain + +**`tests/hooks/test-detect-resume.sh`** (~8-9 assertions): +- Exits 0 when no `.agent-team/` directories exist +- Silent when all workspaces fully completed +- Resume context output when incomplete workspace found +- Staleness detection (touch file after `completed_at`) +- "Valid (unverified)" when `git` unavailable +- Multiple incomplete workspaces listed (most recent first) + +**`tests/hooks/test-check-integration-point.sh`** (~8-9 assertions): +- Exits 0 when no `task-graph.json` exists +- Silent when no convergence points fully unblocked +- Nudge output when all `depends_on` tasks of a convergence point are completed +- Skip when convergence point already `in_progress` or `completed` + +### Existing Test Updates + +- `tests/structure/test-plugin-structure.sh`: assert 3 new scripts exist and are executable, update hook count 6 → 9 +- `tests/structure/test-doc-references.sh`: assert `task-graph.json` is referenced in `workspace-templates.md`, assert all 5 `skills/*/SKILL.md` files reference step 4a, assert `compute-critical-path.sh` and `check-integration-point.sh` and `detect-resume.sh` are referenced in `shared-phases.md` or `coordination-patterns.md` +- `tests/run-tests.sh`: include 3 new test files + +### Expected Counts + +Before: 9 test files, 78 assertions +After: 12 test files, ~103 assertions + +## File Inventory + +### New Files (6) + +| File | Purpose | +|---|---| +| `scripts/compute-critical-path.sh` | TaskCompleted hook — recompute and display critical path | +| `scripts/detect-resume.sh` | SessionStart hook — detect resumable workspaces with staleness validation | +| `scripts/check-integration-point.sh` | TaskCompleted hook — detect and nudge on convergence point completion | +| `tests/hooks/test-compute-critical-path.sh` | Tests for critical path hook | +| `tests/hooks/test-detect-resume.sh` | Tests for resume detection hook | +| `tests/hooks/test-check-integration-point.sh` | Tests for integration checkpoint hook | + +### Modified Files (19) + +| File | Changes | +|---|---| +| `hooks/hooks.json` | Add 3 new hook entries (TaskCompleted ×2, SessionStart ×1) | +| `docs/shared-phases.md` | Phase 1b step 5a, Phase 2 critical path display, Phase 3 steps 1a + 4a, Phase 4 critical path awareness + integration processing | +| `docs/workspace-templates.md` | `task-graph.json` section, update protocol rows, `tasks.md` CP column | +| `docs/coordination-patterns.md` | Resume from Existing Workspace section, Integration Checkpoint Response section | +| `docs/coordination-advanced.md` | Deadline Escalation critical-path integration | +| `docs/report-format.md` | Team Metrics additions, Task Ledger CP column | +| `skills/agent-team/SKILL.md` | Phase 3 step 4a reference | +| `skills/agent-implement/SKILL.md` | Phase 3 step 4a reference, completion gate #4 update | +| `skills/agent-research/SKILL.md` | Phase 3 step 4a reference | +| `skills/agent-audit/SKILL.md` | Phase 3 step 4a reference | +| `skills/agent-plan/SKILL.md` | Phase 3 step 4a reference | +| `README.md` | Workspace tree, hooks section, plugin structure | +| `CLAUDE.md` | File ownership, hook/script counts, verification scenarios | +| `CHANGELOG.md` | Add v2.6.0 entry | +| `.claude-plugin/plugin.json` | Bump version to 2.6.0 | +| `.claude-plugin/marketplace.json` | Bump version to 2.6.0 | +| `tests/structure/test-plugin-structure.sh` | New script assertions, hook count update | +| `tests/structure/test-doc-references.sh` | Add `task-graph.json`, step 4a, and new script reference assertions | +| `tests/run-tests.sh` | Include 3 new test files | + +## Non-Goals + +- **Automated task-graph.json maintenance by hooks** — the lead maintains this file, not the hooks. Hooks read it; the lead writes it. This matches the existing pattern (lead writes `tasks.md`, hooks read it). +- **Weighted critical path** — using estimated task duration instead of node count. This would require effort estimation, which the plugin deliberately avoids. Node-count critical path is a good-enough heuristic. +- **Automatic resume without user choice** — the user always gets to choose resume vs start fresh. Automatic behavior risks silently reusing stale work. +- **Hook-enforced integration gates** — integration checkpoints are informational nudges, not blocking gates. The lead decides whether to verify. This matches the TeammateIdle pattern. + +## Forward Compatibility + +Pre-v2.6.0 workspaces (created without `task-graph.json`) will gracefully degrade: +- All three new hook scripts check for `task-graph.json` existence and exit 0 silently if not found +- Existing workspaces continue to function with the original 3 tracking files (`progress.md`, `tasks.md`, `issues.md`) +- No migration is required — old workspaces simply don't benefit from critical path tracking, resume validation, or integration checkpoints +- If a user wants to add DAG features to an existing workspace, the lead can manually create `task-graph.json` from the current `tasks.md` state From 8b4b93ad2eda4d5c71134648b64a73cef591e74e Mon Sep 17 00:00:00 2001 From: ducdmdev Date: Fri, 20 Mar 2026 09:43:48 +0700 Subject: [PATCH 02/22] docs: add DAG-aligned improvements implementation plan 15 tasks across 3 chunks: scripts+tests, documentation, release. Covers compute-critical-path.sh, detect-resume.sh, check-integration-point.sh, task-graph.json schema, and updates to all 19 modified files. --- .../2026-03-20-dag-aligned-improvements.md | 1541 +++++++++++++++++ 1 file changed, 1541 insertions(+) create mode 100644 docs/plans/2026-03-20-dag-aligned-improvements.md diff --git a/docs/plans/2026-03-20-dag-aligned-improvements.md b/docs/plans/2026-03-20-dag-aligned-improvements.md new file mode 100644 index 0000000..2b41d82 --- /dev/null +++ b/docs/plans/2026-03-20-dag-aligned-improvements.md @@ -0,0 +1,1541 @@ +# DAG-Aligned Improvements Implementation Plan + +> **For agentic workers:** REQUIRED: Use superpowers:subagent-driven-development (if subagents available) or superpowers:executing-plans to implement this plan. Steps use checkbox (`- [ ]`) syntax for tracking. + +**Goal:** Add critical path identification, task-level resume, and early integration checkpoints to the agent-team-plugin (v2.6.0), all powered by a shared `task-graph.json` DAG file. + +**Architecture:** Three new bash hook scripts read from a centralized `task-graph.json` workspace file that the lead maintains. Each script attaches to an existing hook event (TaskCompleted or SessionStart) as a separate entry. Documentation updates propagate the new concepts (critical path, convergence points, resume) through all 5 phases. + +**Tech Stack:** Bash scripts, jq for JSON parsing, git for staleness checks. All markdown documentation. + +**Spec:** `docs/specs/2026-03-20-dag-aligned-improvements-design.md` + +--- + +## Chunk 1: Scripts + Tests + +### Task 1: Add test helper for mock task-graph.json + +**Files:** +- Modify: `tests/lib/test-helpers.sh` + +- [ ] **Step 1: Add `setup_mock_task_graph` helper** + +Add after `setup_mock_workspace` function in `tests/lib/test-helpers.sh`: + +```bash +# --- Mock task-graph.json --- +# Creates a task-graph.json inside an existing mock workspace. +# Usage: setup_mock_task_graph "my-team" '{...json...}' +# If no JSON provided, creates a default 4-task graph (2 parallel + 1 convergence + 1 review) +setup_mock_task_graph() { + local team_name="$1" + local custom_json="${2:-}" + local graph_file="$TEST_TEMP_DIR/.agent-team/$team_name/task-graph.json" + + if [ -n "$custom_json" ]; then + echo "$custom_json" > "$graph_file" + return + fi + + cat > "$graph_file" <<'GRAPH' +{ + "team": "test", + "created": "2026-03-20T10:00:00Z", + "updated": "2026-03-20T10:00:00Z", + "nodes": { + "#1": { + "subject": "Implement auth", + "owner": "impl-1", + "status": "pending", + "depends_on": [], + "completed_at": null, + "output_files": ["src/auth.ts"], + "critical_path": true, + "convergence_point": false + }, + "#2": { + "subject": "Implement session", + "owner": "impl-2", + "status": "pending", + "depends_on": [], + "completed_at": null, + "output_files": ["src/session.ts"], + "critical_path": false, + "convergence_point": false + }, + "#3": { + "subject": "Integrate middleware", + "owner": "impl-1", + "status": "pending", + "depends_on": ["#1", "#2"], + "completed_at": null, + "output_files": ["src/middleware.ts"], + "critical_path": true, + "convergence_point": true + }, + "#4": { + "subject": "Review all", + "owner": "reviewer", + "status": "pending", + "depends_on": ["#3"], + "completed_at": null, + "output_files": [], + "critical_path": true, + "convergence_point": false + } + }, + "critical_path": ["#1", "#3", "#4"], + "critical_path_length": 3 +} +GRAPH +} + +# --- Run hook capturing stdout separately --- +# Like run_hook but also captures stdout (needed for detect-resume.sh which outputs to stdout). +# Usage: run_hook_full "$script" "$json_input" +# Sets: HOOK_EXIT, HOOK_STDOUT, HOOK_STDERR +HOOK_STDOUT="" + +run_hook_full() { + local script="$1" + local input="$2" + local stdout_file + stdout_file=$(mktemp "${TMPDIR:-/tmp}/hook-stdout.XXXXXX") + HOOK_STDERR=$(echo "$input" | bash "$script" 2>&1 1>"$stdout_file") + HOOK_EXIT=$? + HOOK_STDOUT=$(cat "$stdout_file") + rm -f "$stdout_file" +} + +assert_stdout_contains() { + local pattern="$1" + local stdout_output="$2" + local test_name="$3" + TESTS_TOTAL=$((TESTS_TOTAL + 1)) + if echo "$stdout_output" | grep -qi "$pattern"; then + TESTS_PASSED=$((TESTS_PASSED + 1)) + printf " ${GREEN}PASS${RESET} %s\n" "$test_name" + else + TESTS_FAILED=$((TESTS_FAILED + 1)) + printf " ${RED}FAIL${RESET} %s (stdout missing pattern '%s')\n" "$test_name" "$pattern" + printf " stdout was: %s\n" "$stdout_output" + fi +} +``` + +- [ ] **Step 2: Commit** + +```bash +git add tests/lib/test-helpers.sh +git commit -m "test: add task-graph.json mock helper and stdout capture for DAG hooks" +``` + +### Task 2: Write `compute-critical-path.sh` + tests + +**Files:** +- Create: `scripts/compute-critical-path.sh` +- Create: `tests/hooks/test-compute-critical-path.sh` + +- [ ] **Step 1: Write the test file** + +Create `tests/hooks/test-compute-critical-path.sh`: + +```bash +#!/bin/bash +# Tests for scripts/compute-critical-path.sh (TaskCompleted hook — critical path) + +source "$(dirname "$0")/../lib/test-helpers.sh" + +HOOK="$PROJECT_ROOT/scripts/compute-critical-path.sh" + +echo "Critical path hook tests" +echo "========================" + +if ! command -v jq &>/dev/null; then + printf " ${YELLOW}SKIP${RESET} all — jq not installed\n" + exit 0 +fi + +# --- Test 1: No task-graph.json exits 0 silently --- +setup_temp_dir +cd "$TEST_TEMP_DIR" +setup_mock_workspace "test" +run_hook "$HOOK" '{"cwd":"'"$TEST_TEMP_DIR"'","team_name":"test"}' +assert_exit_code 0 "$HOOK_EXIT" "1: No task-graph.json exits 0 silently" +cleanup_temp_dir + +# --- Test 2: Empty input exits 0 --- +setup_temp_dir +cd "$TEST_TEMP_DIR" +run_hook "$HOOK" '{}' +assert_exit_code 0 "$HOOK_EXIT" "2: Empty input exits 0" +cleanup_temp_dir + +# --- Test 3: Critical path task completes — shows remaining path --- +setup_temp_dir +cd "$TEST_TEMP_DIR" +setup_mock_workspace "test" +# Mark #1 as completed in the graph +setup_mock_task_graph "test" "$(cat <<'JSON' +{ + "team": "test", + "created": "2026-03-20T10:00:00Z", + "updated": "2026-03-20T10:30:00Z", + "nodes": { + "#1": {"subject":"Auth","owner":"impl-1","status":"completed","depends_on":[],"completed_at":"2026-03-20T10:30:00Z","output_files":["src/auth.ts"],"critical_path":true,"convergence_point":false}, + "#2": {"subject":"Session","owner":"impl-2","status":"pending","depends_on":[],"completed_at":null,"output_files":["src/session.ts"],"critical_path":false,"convergence_point":false}, + "#3": {"subject":"Middleware","owner":"impl-1","status":"pending","depends_on":["#1","#2"],"completed_at":null,"output_files":["src/mw.ts"],"critical_path":true,"convergence_point":true}, + "#4": {"subject":"Review","owner":"reviewer","status":"pending","depends_on":["#3"],"completed_at":null,"output_files":[],"critical_path":true,"convergence_point":false} + }, + "critical_path": ["#1","#3","#4"], + "critical_path_length": 3 +} +JSON +)" +run_hook "$HOOK" '{"cwd":"'"$TEST_TEMP_DIR"'","team_name":"test"}' +assert_exit_code 0 "$HOOK_EXIT" "3: Critical path task complete exits 0" +assert_stderr_contains "Remaining critical path" "$HOOK_STDERR" "3: Shows remaining critical path" +cleanup_temp_dir + +# --- Test 4: Non-critical task completes — shows unchanged path --- +setup_temp_dir +cd "$TEST_TEMP_DIR" +setup_mock_workspace "test" +setup_mock_task_graph "test" "$(cat <<'JSON' +{ + "team": "test", + "created": "2026-03-20T10:00:00Z", + "updated": "2026-03-20T10:30:00Z", + "nodes": { + "#1": {"subject":"Auth","owner":"impl-1","status":"pending","depends_on":[],"completed_at":null,"output_files":["src/auth.ts"],"critical_path":true,"convergence_point":false}, + "#2": {"subject":"Session","owner":"impl-2","status":"completed","depends_on":[],"completed_at":"2026-03-20T10:30:00Z","output_files":["src/session.ts"],"critical_path":false,"convergence_point":false}, + "#3": {"subject":"Middleware","owner":"impl-1","status":"pending","depends_on":["#1","#2"],"completed_at":null,"output_files":["src/mw.ts"],"critical_path":true,"convergence_point":true} + }, + "critical_path": ["#1","#3"], + "critical_path_length": 2 +} +JSON +)" +run_hook "$HOOK" '{"cwd":"'"$TEST_TEMP_DIR"'","team_name":"test"}' +assert_exit_code 0 "$HOOK_EXIT" "4: Non-critical task complete exits 0" +assert_stderr_contains "not on critical path" "$HOOK_STDERR" "4: Shows not on critical path" +cleanup_temp_dir + +# --- Test 5: All tasks complete — no critical path --- +setup_temp_dir +cd "$TEST_TEMP_DIR" +setup_mock_workspace "test" +setup_mock_task_graph "test" "$(cat <<'JSON' +{ + "team": "test", + "created": "2026-03-20T10:00:00Z", + "updated": "2026-03-20T11:00:00Z", + "nodes": { + "#1": {"subject":"Auth","owner":"impl-1","status":"completed","depends_on":[],"completed_at":"2026-03-20T10:30:00Z","output_files":["src/auth.ts"],"critical_path":false,"convergence_point":false}, + "#2": {"subject":"Session","owner":"impl-2","status":"completed","depends_on":[],"completed_at":"2026-03-20T10:45:00Z","output_files":["src/session.ts"],"critical_path":false,"convergence_point":false} + }, + "critical_path": [], + "critical_path_length": 0 +} +JSON +)" +run_hook "$HOOK" '{"cwd":"'"$TEST_TEMP_DIR"'","team_name":"test"}' +assert_exit_code 0 "$HOOK_EXIT" "5: All complete exits 0" +assert_stderr_contains "all remaining tasks can run in parallel\|No critical path" "$HOOK_STDERR" "5: Shows no critical path" +cleanup_temp_dir + +# --- Test 6: Malformed JSON — warns but exits 0 --- +setup_temp_dir +cd "$TEST_TEMP_DIR" +setup_mock_workspace "test" +echo "NOT VALID JSON" > "$TEST_TEMP_DIR/.agent-team/test/task-graph.json" +run_hook "$HOOK" '{"cwd":"'"$TEST_TEMP_DIR"'","team_name":"test"}' +assert_exit_code 0 "$HOOK_EXIT" "6: Malformed JSON exits 0" +assert_stderr_contains "warning\|parse" "$HOOK_STDERR" "6: Warns about parse failure" +cleanup_temp_dir + +# --- Test 7: Remediation team -fix suffix fallback --- +setup_temp_dir +cd "$TEST_TEMP_DIR" +setup_mock_workspace "test" +setup_mock_task_graph "test" +run_hook "$HOOK" '{"cwd":"'"$TEST_TEMP_DIR"'","team_name":"test-fix"}' +assert_exit_code 0 "$HOOK_EXIT" "7: -fix suffix falls back to base workspace" +cleanup_temp_dir + +# --- Test 8: Blocked critical task warning --- +setup_temp_dir +cd "$TEST_TEMP_DIR" +setup_mock_workspace "test" +setup_mock_task_graph "test" "$(cat <<'JSON' +{ + "team": "test", + "created": "2026-03-20T10:00:00Z", + "updated": "2026-03-20T10:30:00Z", + "nodes": { + "#1": {"subject":"Auth","owner":"impl-1","status":"completed","depends_on":[],"completed_at":"2026-03-20T10:30:00Z","output_files":["src/auth.ts"],"critical_path":true,"convergence_point":false}, + "#2": {"subject":"Session","owner":"impl-2","status":"pending","depends_on":[],"completed_at":null,"output_files":["src/session.ts"],"critical_path":false,"convergence_point":false}, + "#3": {"subject":"Middleware","owner":"impl-1","status":"blocked","depends_on":["#1","#2"],"completed_at":null,"output_files":["src/mw.ts"],"critical_path":true,"convergence_point":true} + }, + "critical_path": ["#1","#3"], + "critical_path_length": 2 +} +JSON +)" +run_hook "$HOOK" '{"cwd":"'"$TEST_TEMP_DIR"'","team_name":"test"}' +assert_exit_code 0 "$HOOK_EXIT" "8: Blocked critical task exits 0" +assert_stderr_contains "blocked" "$HOOK_STDERR" "8: Warns about blocked critical task" +cleanup_temp_dir + +print_summary +exit "$TESTS_FAILED" +``` + +- [ ] **Step 2: Run tests — verify all fail (script doesn't exist yet)** + +```bash +bash tests/hooks/test-compute-critical-path.sh +``` + +Expected: failures (script not found) + +- [ ] **Step 3: Write `compute-critical-path.sh`** + +Create `scripts/compute-critical-path.sh`: + +```bash +#!/bin/bash +# Hook: TaskCompleted +# Recomputes and displays the critical path from task-graph.json. +# Informational only — always exits 0. + +# Graceful jq fallback +if ! command -v jq &>/dev/null; then + exit 0 +fi + +INPUT=$(cat) +CWD=$(echo "$INPUT" | jq -r '.cwd // empty') +TEAM_NAME=$(echo "$INPUT" | jq -r '.team_name // empty') + +# Need both cwd and team_name to locate task-graph.json +if [ -z "$CWD" ] || [ -z "$TEAM_NAME" ]; then + exit 0 +fi + +# Resolve workspace path (with -fix suffix fallback for remediation teams) +GRAPH_FILE="$CWD/.agent-team/$TEAM_NAME/task-graph.json" +if [ ! -f "$GRAPH_FILE" ]; then + BASE_NAME="${TEAM_NAME%-fix}" + if [ "$BASE_NAME" != "$TEAM_NAME" ] && [ -f "$CWD/.agent-team/$BASE_NAME/task-graph.json" ]; then + GRAPH_FILE="$CWD/.agent-team/$BASE_NAME/task-graph.json" + else + exit 0 + fi +fi + +# Parse JSON — warn if malformed +GRAPH=$(jq '.' "$GRAPH_FILE" 2>/dev/null) +if [ -z "$GRAPH" ]; then + echo "Warning: task-graph.json exists but failed to parse. Critical path hooks disabled until JSON is fixed." >&2 + exit 0 +fi + +# Count remaining (non-completed) nodes +REMAINING=$(echo "$GRAPH" | jq '[.nodes | to_entries[] | select(.value.status != "completed")] | length') + +if [ "$REMAINING" -eq 0 ]; then + echo "No critical path — all remaining tasks can run in parallel." >&2 + exit 0 +fi + +# Find which nodes just completed (status=completed) and were on critical path +# Build the remaining dependency chains to find the longest path +# Use jq to compute: for each remaining node, trace depends_on depth + +# Get all remaining node IDs +REMAINING_IDS=$(echo "$GRAPH" | jq -r '[.nodes | to_entries[] | select(.value.status != "completed") | .key] | .[]') + +# DFS longest path computation via jq +# For each remaining node, compute depth = 1 + max(depth of remaining dependencies) +# Cycle guard: track visited nodes +CRITICAL_PATH=$(echo "$GRAPH" | jq -r ' + def depth(id; visited): + if (visited | index(id)) then 0 # cycle guard + elif (.nodes[id].status == "completed") then 0 + else + .nodes[id].depends_on as $deps | + ([$deps[] | select(. as $d | .nodes[$d].status != "completed") | depth(.; visited + [id])] | max // 0) + 1 + end; + + [.nodes | to_entries[] | select(.value.status != "completed") | + {id: .key, depth: depth(.key; [])} + ] | sort_by(-.depth, .id) | + if length == 0 then empty + else + .[0] as $root | + # Trace the path from the deepest node forward through its remaining deps + def trace_path(id): + if (.nodes[id].status == "completed") then [] + else + .nodes[id].depends_on as $deps | + [$deps[] | select(. as $d | .nodes[$d].status != "completed")] as $remaining_deps | + if ($remaining_deps | length) == 0 then [id] + else + ([$remaining_deps[] | {id: ., depth: depth(.; [])}] | sort_by(-.depth, .id) | .[0].id) as $next | + trace_path($next) + [id] + end + end; + trace_path($root.id) | join(" → ") + end +' 2>/dev/null) + +# Get the previously recorded critical path +OLD_CP=$(echo "$GRAPH" | jq -r '.critical_path | join(" → ")') +CP_LENGTH=$(echo "$CRITICAL_PATH" | tr '→' '\n' | sed 's/ //g' | grep -c '.') + +# Check if any completed node was on the old critical path +WAS_ON_CP=false +COMPLETED_NODES=$(echo "$GRAPH" | jq -r '[.nodes | to_entries[] | select(.value.status == "completed") | .key] | .[]') +OLD_CP_NODES=$(echo "$GRAPH" | jq -r '.critical_path[]') +for node in $COMPLETED_NODES; do + if echo "$OLD_CP_NODES" | grep -q "^${node}$"; then + WAS_ON_CP=true + break + fi +done + +# Find next critical task details +NEXT_CRITICAL=$(echo "$CRITICAL_PATH" | cut -d'→' -f1 | sed 's/ //g') +if [ -n "$NEXT_CRITICAL" ]; then + NEXT_SUBJECT=$(echo "$GRAPH" | jq -r --arg id "$NEXT_CRITICAL" '.nodes[$id].subject // "unknown"') + NEXT_OWNER=$(echo "$GRAPH" | jq -r --arg id "$NEXT_CRITICAL" '.nodes[$id].owner // "unassigned"') + NEXT_STATUS=$(echo "$GRAPH" | jq -r --arg id "$NEXT_CRITICAL" '.nodes[$id].status // "unknown"') +fi + +if [ "$WAS_ON_CP" = true ]; then + echo "Critical path update: completed task was on critical path." >&2 + echo "Remaining critical path: $CRITICAL_PATH (length: $CP_LENGTH)" >&2 + if [ -n "$NEXT_CRITICAL" ]; then + echo "Next critical task: $NEXT_CRITICAL $NEXT_SUBJECT (owner: $NEXT_OWNER, status: $NEXT_STATUS)" >&2 + if [ "$NEXT_STATUS" = "blocked" ]; then + BLOCKED_BY=$(echo "$GRAPH" | jq -r --arg id "$NEXT_CRITICAL" '[.nodes[$id].depends_on[] | select(. as $d | .nodes[$d].status != "completed")] | join(", ")' 2>/dev/null) + echo "⚠ Critical task $NEXT_CRITICAL is blocked — resolve blocker(s) ${BLOCKED_BY:-unknown} to maintain throughput." >&2 + fi + fi +else + echo "Task completed (not on critical path). Critical path unchanged: $OLD_CP (length: $(echo "$GRAPH" | jq '.critical_path_length'))" >&2 +fi + +exit 0 +``` + +- [ ] **Step 4: Make executable** + +```bash +chmod +x scripts/compute-critical-path.sh +``` + +- [ ] **Step 5: Run tests — verify they pass** + +```bash +bash tests/hooks/test-compute-critical-path.sh +``` + +Expected: all 8 tests pass + +- [ ] **Step 6: Commit** + +```bash +git add scripts/compute-critical-path.sh tests/hooks/test-compute-critical-path.sh +git commit -m "feat: add compute-critical-path.sh hook with tests" +``` + +### Task 3: Write `detect-resume.sh` + tests + +**Files:** +- Create: `scripts/detect-resume.sh` +- Create: `tests/hooks/test-detect-resume.sh` + +- [ ] **Step 1: Write the test file** + +Create `tests/hooks/test-detect-resume.sh`: + +```bash +#!/bin/bash +# Tests for scripts/detect-resume.sh (SessionStart hook — resume detection) + +source "$(dirname "$0")/../lib/test-helpers.sh" + +HOOK="$PROJECT_ROOT/scripts/detect-resume.sh" + +echo "Resume detection hook tests" +echo "============================" + +if ! command -v jq &>/dev/null; then + printf " ${YELLOW}SKIP${RESET} all — jq not installed\n" + exit 0 +fi + +# --- Test 1: No .agent-team/ directories — silent exit 0 --- +setup_temp_dir +cd "$TEST_TEMP_DIR" +run_hook_full "$HOOK" '{"cwd":"'"$TEST_TEMP_DIR"'"}' +assert_exit_code 0 "$HOOK_EXIT" "1: No workspaces exits 0" +TESTS_TOTAL=$((TESTS_TOTAL + 1)) +if [ -z "$HOOK_STDOUT" ]; then + TESTS_PASSED=$((TESTS_PASSED + 1)) + printf " ${GREEN}PASS${RESET} 1: Silent when no workspaces\n" +else + TESTS_FAILED=$((TESTS_FAILED + 1)) + printf " ${RED}FAIL${RESET} 1: Expected silent, got stdout: %s\n" "$HOOK_STDOUT" +fi +cleanup_temp_dir + +# --- Test 2: All completed workspace — silent --- +setup_temp_dir +cd "$TEST_TEMP_DIR" +setup_mock_workspace "test" +setup_mock_task_graph "test" "$(cat <<'JSON' +{ + "team": "test", + "created": "2026-03-20T10:00:00Z", + "updated": "2026-03-20T11:00:00Z", + "nodes": { + "#1": {"subject":"Auth","owner":"impl-1","status":"completed","depends_on":[],"completed_at":"2026-03-20T10:30:00Z","output_files":["src/auth.ts"],"critical_path":false,"convergence_point":false} + }, + "critical_path": [], + "critical_path_length": 0 +} +JSON +)" +run_hook_full "$HOOK" '{"cwd":"'"$TEST_TEMP_DIR"'"}' +assert_exit_code 0 "$HOOK_EXIT" "2: All completed exits 0" +TESTS_TOTAL=$((TESTS_TOTAL + 1)) +if [ -z "$HOOK_STDOUT" ]; then + TESTS_PASSED=$((TESTS_PASSED + 1)) + printf " ${GREEN}PASS${RESET} 2: Silent when all complete\n" +else + TESTS_FAILED=$((TESTS_FAILED + 1)) + printf " ${RED}FAIL${RESET} 2: Expected silent, got stdout: %s\n" "$HOOK_STDOUT" +fi +cleanup_temp_dir + +# --- Test 3: Incomplete workspace — shows resume context --- +setup_temp_dir +cd "$TEST_TEMP_DIR" +setup_mock_workspace "test" +setup_mock_task_graph "test" +run_hook_full "$HOOK" '{"cwd":"'"$TEST_TEMP_DIR"'"}' +assert_exit_code 0 "$HOOK_EXIT" "3: Incomplete workspace exits 0" +assert_stdout_contains "Resumable workspace" "$HOOK_STDOUT" "3: Shows resumable workspace" +cleanup_temp_dir + +# --- Test 4: Staleness detection — file modified after completion --- +setup_temp_dir +cd "$TEST_TEMP_DIR" +setup_mock_workspace "test" +setup_mock_git_repo "clean" +# Create the output file and commit it +echo "original" > src/auth.ts 2>/dev/null || (mkdir -p src && echo "original" > src/auth.ts) +(cd "$TEST_TEMP_DIR" && git add src/auth.ts && git commit -q -m "add auth") +# Create graph with #1 completed in the past +setup_mock_task_graph "test" "$(cat <<'JSON' +{ + "team": "test", + "created": "2026-03-20T10:00:00Z", + "updated": "2026-03-20T10:30:00Z", + "nodes": { + "#1": {"subject":"Auth","owner":"impl-1","status":"completed","depends_on":[],"completed_at":"2026-01-01T00:00:00Z","output_files":["src/auth.ts"],"critical_path":true,"convergence_point":false}, + "#2": {"subject":"Session","owner":"impl-2","status":"pending","depends_on":[],"completed_at":null,"output_files":["src/session.ts"],"critical_path":false,"convergence_point":false} + }, + "critical_path": ["#1"], + "critical_path_length": 1 +} +JSON +)" +# Modify the file AFTER the completed_at timestamp +sleep 1 +echo "modified" > "$TEST_TEMP_DIR/src/auth.ts" +(cd "$TEST_TEMP_DIR" && git add src/auth.ts && git commit -q -m "modify auth") +run_hook_full "$HOOK" '{"cwd":"'"$TEST_TEMP_DIR"'"}' +assert_exit_code 0 "$HOOK_EXIT" "4: Stale detection exits 0" +assert_stdout_contains "stale" "$HOOK_STDOUT" "4: Detects stale output file" +cleanup_temp_dir + +# --- Test 5: Empty cwd falls back to current dir --- +setup_temp_dir +cd "$TEST_TEMP_DIR" +setup_mock_workspace "test" +setup_mock_task_graph "test" +run_hook_full "$HOOK" '{"cwd":""}' +assert_exit_code 0 "$HOOK_EXIT" "5: Empty cwd exits 0" +cleanup_temp_dir + +# --- Test 6: Empty input exits 0 --- +setup_temp_dir +cd "$TEST_TEMP_DIR" +run_hook_full "$HOOK" '{}' +assert_exit_code 0 "$HOOK_EXIT" "6: Empty input exits 0" +cleanup_temp_dir + +print_summary +exit "$TESTS_FAILED" +``` + +- [ ] **Step 2: Run tests — verify they fail** + +```bash +bash tests/hooks/test-detect-resume.sh +``` + +Expected: failures (script not found) + +- [ ] **Step 3: Write `detect-resume.sh`** + +Create `scripts/detect-resume.sh`: + +```bash +#!/bin/bash +# Hook: SessionStart (no matcher — fires on all session starts) +# Detects existing workspaces with incomplete tasks and validates staleness. +# Output goes to stdout (injected into conversation context, matching recover-context.sh). +# Always exits 0 (informational only). + +# Graceful jq fallback +if ! command -v jq &>/dev/null; then + exit 0 +fi + +INPUT=$(cat) +CWD=$(echo "$INPUT" | jq -r '.cwd // empty') +CWD="${CWD:-.}" + +# Scan for task-graph.json files +GRAPHS=() +for graph_file in "$CWD"/.agent-team/*/task-graph.json; do + [ -f "$graph_file" ] || continue + GRAPHS+=("$graph_file") +done + +if [ ${#GRAPHS[@]} -eq 0 ]; then + exit 0 +fi + +# Sort by updated timestamp (most recent first) +SORTED_GRAPHS=() +while IFS= read -r line; do + SORTED_GRAPHS+=("$line") +done < <( + for g in "${GRAPHS[@]}"; do + ts=$(jq -r '.updated // .created // "1970-01-01"' "$g" 2>/dev/null) + echo "$ts|$g" + done | sort -r | cut -d'|' -f2 +) + +HAS_OUTPUT=false + +for graph_file in "${SORTED_GRAPHS[@]}"; do + GRAPH=$(jq '.' "$graph_file" 2>/dev/null) + [ -z "$GRAPH" ] && continue + + TEAM=$(echo "$GRAPH" | jq -r '.team // "unknown"') + WORKSPACE_DIR=$(dirname "$graph_file") + + # Count total and completed + TOTAL=$(echo "$GRAPH" | jq '[.nodes | to_entries[]] | length') + COMPLETED=$(echo "$GRAPH" | jq '[.nodes | to_entries[] | select(.value.status == "completed")] | length') + REMAINING=$((TOTAL - COMPLETED)) + + # Skip fully completed workspaces + if [ "$REMAINING" -eq 0 ]; then + continue + fi + + # Validate completed tasks for staleness + VALID_LIST="" + STALE_LIST="" + REMAINING_LIST="" + + while IFS= read -r entry; do + ID=$(echo "$entry" | jq -r '.key') + STATUS=$(echo "$entry" | jq -r '.value.status') + SUBJECT=$(echo "$entry" | jq -r '.value.subject') + + if [ "$STATUS" = "completed" ]; then + COMPLETED_AT=$(echo "$entry" | jq -r '.value.completed_at // empty') + OUTPUT_FILES=$(echo "$entry" | jq -r '.value.output_files[]' 2>/dev/null) + IS_STALE=false + + if [ -n "$OUTPUT_FILES" ] && [ -n "$COMPLETED_AT" ] && command -v git &>/dev/null; then + while IFS= read -r ofile; do + [ -z "$ofile" ] && continue + FULL_PATH="$CWD/$ofile" + if [ -f "$FULL_PATH" ]; then + FILE_DATE=$(cd "$CWD" && git log -1 --format=%cI -- "$ofile" 2>/dev/null) + if [ -n "$FILE_DATE" ] && [[ "$FILE_DATE" > "$COMPLETED_AT" ]]; then + IS_STALE=true + STALE_LIST="${STALE_LIST} Completed (stale): $ID ($SUBJECT) — $ofile modified after completion\n" + break + fi + fi + done <<< "$OUTPUT_FILES" + fi + + if [ "$IS_STALE" = false ]; then + if command -v git &>/dev/null; then + VALID_LIST="${VALID_LIST} Completed (valid): $ID ($SUBJECT) — output files unchanged\n" + else + VALID_LIST="${VALID_LIST} Completed (valid, unverified): $ID ($SUBJECT) — git unavailable\n" + fi + fi + else + REMAINING_LIST="${REMAINING_LIST} Remaining: $ID ($SUBJECT) — status: $STATUS\n" + fi + done < <(echo "$GRAPH" | jq -c '.nodes | to_entries[]') + + # Output resume context to stdout + HAS_OUTPUT=true + REL_PATH="${WORKSPACE_DIR#$CWD/}" + echo "" + echo "Resumable workspace found: $REL_PATH/" + echo " Tasks: $COMPLETED/$TOTAL completed, $REMAINING remaining" + [ -n "$VALID_LIST" ] && printf "$VALID_LIST" + [ -n "$STALE_LIST" ] && printf "$STALE_LIST" + [ -n "$REMAINING_LIST" ] && printf "$REMAINING_LIST" + + # Show remaining critical path if available + CP=$(echo "$GRAPH" | jq -r '[.critical_path[] | select(. as $id | .nodes[$id].status != "completed")] | join(" → ")' 2>/dev/null) + [ -n "$CP" ] && echo " Critical path (remaining): $CP" + + echo "" + echo " To resume: \"resume team $TEAM\"" + echo " To start fresh: proceed normally (existing workspace will be archived)" +done + +exit 0 +``` + +- [ ] **Step 4: Make executable** + +```bash +chmod +x scripts/detect-resume.sh +``` + +- [ ] **Step 5: Run tests — verify they pass** + +```bash +bash tests/hooks/test-detect-resume.sh +``` + +Expected: all 6 tests pass + +- [ ] **Step 6: Commit** + +```bash +git add scripts/detect-resume.sh tests/hooks/test-detect-resume.sh +git commit -m "feat: add detect-resume.sh hook with staleness validation and tests" +``` + +### Task 4: Write `check-integration-point.sh` + tests + +**Files:** +- Create: `scripts/check-integration-point.sh` +- Create: `tests/hooks/test-check-integration-point.sh` + +- [ ] **Step 1: Write the test file** + +Create `tests/hooks/test-check-integration-point.sh`: + +```bash +#!/bin/bash +# Tests for scripts/check-integration-point.sh (TaskCompleted hook — integration checkpoints) + +source "$(dirname "$0")/../lib/test-helpers.sh" + +HOOK="$PROJECT_ROOT/scripts/check-integration-point.sh" + +echo "Integration checkpoint hook tests" +echo "===================================" + +if ! command -v jq &>/dev/null; then + printf " ${YELLOW}SKIP${RESET} all — jq not installed\n" + exit 0 +fi + +# --- Test 1: No task-graph.json exits 0 silently --- +setup_temp_dir +cd "$TEST_TEMP_DIR" +setup_mock_workspace "test" +run_hook "$HOOK" '{"cwd":"'"$TEST_TEMP_DIR"'","team_name":"test"}' +assert_exit_code 0 "$HOOK_EXIT" "1: No task-graph.json exits 0 silently" +cleanup_temp_dir + +# --- Test 2: No convergence points fully unblocked — silent --- +setup_temp_dir +cd "$TEST_TEMP_DIR" +setup_mock_workspace "test" +# #1 completed, #2 still pending, #3 is convergence but not all deps done +setup_mock_task_graph "test" "$(cat <<'JSON' +{ + "team": "test", + "created": "2026-03-20T10:00:00Z", + "updated": "2026-03-20T10:30:00Z", + "nodes": { + "#1": {"subject":"Auth","owner":"impl-1","status":"completed","depends_on":[],"completed_at":"2026-03-20T10:30:00Z","output_files":["src/auth.ts"],"critical_path":true,"convergence_point":false}, + "#2": {"subject":"Session","owner":"impl-2","status":"pending","depends_on":[],"completed_at":null,"output_files":["src/session.ts"],"critical_path":false,"convergence_point":false}, + "#3": {"subject":"Middleware","owner":"impl-1","status":"pending","depends_on":["#1","#2"],"completed_at":null,"output_files":["src/mw.ts"],"critical_path":true,"convergence_point":true} + }, + "critical_path": ["#1","#3"], + "critical_path_length": 2 +} +JSON +)" +run_hook "$HOOK" '{"cwd":"'"$TEST_TEMP_DIR"'","team_name":"test"}' +assert_exit_code 0 "$HOOK_EXIT" "2: Partial convergence exits 0" +TESTS_TOTAL=$((TESTS_TOTAL + 1)) +if [ -z "$HOOK_STDERR" ]; then + TESTS_PASSED=$((TESTS_PASSED + 1)) + printf " ${GREEN}PASS${RESET} 2: Silent when convergence not fully unblocked\n" +else + TESTS_FAILED=$((TESTS_FAILED + 1)) + printf " ${RED}FAIL${RESET} 2: Expected silent, got: %s\n" "$HOOK_STDERR" +fi +cleanup_temp_dir + +# --- Test 3: All deps of convergence point completed — nudge --- +setup_temp_dir +cd "$TEST_TEMP_DIR" +setup_mock_workspace "test" +setup_mock_task_graph "test" "$(cat <<'JSON' +{ + "team": "test", + "created": "2026-03-20T10:00:00Z", + "updated": "2026-03-20T10:45:00Z", + "nodes": { + "#1": {"subject":"Auth","owner":"impl-1","status":"completed","depends_on":[],"completed_at":"2026-03-20T10:30:00Z","output_files":["src/auth.ts"],"critical_path":true,"convergence_point":false}, + "#2": {"subject":"Session","owner":"impl-2","status":"completed","depends_on":[],"completed_at":"2026-03-20T10:45:00Z","output_files":["src/session.ts"],"critical_path":false,"convergence_point":false}, + "#3": {"subject":"Middleware","owner":"impl-1","status":"pending","depends_on":["#1","#2"],"completed_at":null,"output_files":["src/mw.ts"],"critical_path":true,"convergence_point":true} + }, + "critical_path": ["#1","#3"], + "critical_path_length": 2 +} +JSON +)" +run_hook "$HOOK" '{"cwd":"'"$TEST_TEMP_DIR"'","team_name":"test"}' +assert_exit_code 0 "$HOOK_EXIT" "3: Convergence unblocked exits 0" +assert_stderr_contains "Integration checkpoint" "$HOOK_STDERR" "3: Shows integration checkpoint nudge" +assert_stderr_contains "Middleware" "$HOOK_STDERR" "3: Mentions the convergence task" +cleanup_temp_dir + +# --- Test 4: Convergence point already in_progress — skip --- +setup_temp_dir +cd "$TEST_TEMP_DIR" +setup_mock_workspace "test" +setup_mock_task_graph "test" "$(cat <<'JSON' +{ + "team": "test", + "created": "2026-03-20T10:00:00Z", + "updated": "2026-03-20T10:45:00Z", + "nodes": { + "#1": {"subject":"Auth","owner":"impl-1","status":"completed","depends_on":[],"completed_at":"2026-03-20T10:30:00Z","output_files":["src/auth.ts"],"critical_path":true,"convergence_point":false}, + "#2": {"subject":"Session","owner":"impl-2","status":"completed","depends_on":[],"completed_at":"2026-03-20T10:45:00Z","output_files":["src/session.ts"],"critical_path":false,"convergence_point":false}, + "#3": {"subject":"Middleware","owner":"impl-1","status":"in_progress","depends_on":["#1","#2"],"completed_at":null,"output_files":["src/mw.ts"],"critical_path":true,"convergence_point":true} + }, + "critical_path": ["#1","#3"], + "critical_path_length": 2 +} +JSON +)" +run_hook "$HOOK" '{"cwd":"'"$TEST_TEMP_DIR"'","team_name":"test"}' +assert_exit_code 0 "$HOOK_EXIT" "4: In-progress convergence exits 0" +TESTS_TOTAL=$((TESTS_TOTAL + 1)) +if [ -z "$HOOK_STDERR" ]; then + TESTS_PASSED=$((TESTS_PASSED + 1)) + printf " ${GREEN}PASS${RESET} 4: Silent when convergence already in_progress\n" +else + TESTS_FAILED=$((TESTS_FAILED + 1)) + printf " ${RED}FAIL${RESET} 4: Expected silent, got: %s\n" "$HOOK_STDERR" +fi +cleanup_temp_dir + +# --- Test 5: No convergence points in graph — silent --- +setup_temp_dir +cd "$TEST_TEMP_DIR" +setup_mock_workspace "test" +setup_mock_task_graph "test" "$(cat <<'JSON' +{ + "team": "test", + "created": "2026-03-20T10:00:00Z", + "updated": "2026-03-20T10:30:00Z", + "nodes": { + "#1": {"subject":"Auth","owner":"impl-1","status":"completed","depends_on":[],"completed_at":"2026-03-20T10:30:00Z","output_files":["src/auth.ts"],"critical_path":true,"convergence_point":false}, + "#2": {"subject":"Session","owner":"impl-2","status":"pending","depends_on":[],"completed_at":null,"output_files":["src/session.ts"],"critical_path":false,"convergence_point":false} + }, + "critical_path": ["#1"], + "critical_path_length": 1 +} +JSON +)" +run_hook "$HOOK" '{"cwd":"'"$TEST_TEMP_DIR"'","team_name":"test"}' +assert_exit_code 0 "$HOOK_EXIT" "5: No convergence points exits 0" +TESTS_TOTAL=$((TESTS_TOTAL + 1)) +if [ -z "$HOOK_STDERR" ]; then + TESTS_PASSED=$((TESTS_PASSED + 1)) + printf " ${GREEN}PASS${RESET} 5: Silent when no convergence points\n" +else + TESTS_FAILED=$((TESTS_FAILED + 1)) + printf " ${RED}FAIL${RESET} 5: Expected silent, got: %s\n" "$HOOK_STDERR" +fi +cleanup_temp_dir + +# --- Test 6: Empty input exits 0 --- +setup_temp_dir +cd "$TEST_TEMP_DIR" +run_hook "$HOOK" '{}' +assert_exit_code 0 "$HOOK_EXIT" "6: Empty input exits 0" +cleanup_temp_dir + +print_summary +exit "$TESTS_FAILED" +``` + +- [ ] **Step 2: Run tests — verify they fail** + +```bash +bash tests/hooks/test-check-integration-point.sh +``` + +Expected: failures (script not found) + +- [ ] **Step 3: Write `check-integration-point.sh`** + +Create `scripts/check-integration-point.sh`: + +```bash +#!/bin/bash +# Hook: TaskCompleted +# Detects when all upstream tasks of a convergence point complete. +# Nudges the lead to verify interface compatibility. +# Informational only — always exits 0. + +# Graceful jq fallback +if ! command -v jq &>/dev/null; then + exit 0 +fi + +INPUT=$(cat) +CWD=$(echo "$INPUT" | jq -r '.cwd // empty') +TEAM_NAME=$(echo "$INPUT" | jq -r '.team_name // empty') + +if [ -z "$CWD" ] || [ -z "$TEAM_NAME" ]; then + exit 0 +fi + +# Resolve workspace path (with -fix suffix fallback) +GRAPH_FILE="$CWD/.agent-team/$TEAM_NAME/task-graph.json" +if [ ! -f "$GRAPH_FILE" ]; then + BASE_NAME="${TEAM_NAME%-fix}" + if [ "$BASE_NAME" != "$TEAM_NAME" ] && [ -f "$CWD/.agent-team/$BASE_NAME/task-graph.json" ]; then + GRAPH_FILE="$CWD/.agent-team/$BASE_NAME/task-graph.json" + else + exit 0 + fi +fi + +# Parse JSON — warn if malformed +GRAPH=$(jq '.' "$GRAPH_FILE" 2>/dev/null) +if [ -z "$GRAPH" ]; then + echo "Warning: task-graph.json exists but failed to parse. Integration checkpoint hooks disabled until JSON is fixed." >&2 + exit 0 +fi + +# Find convergence points that are fully unblocked and still pending +echo "$GRAPH" | jq -r ' + .nodes | to_entries[] | + select(.value.convergence_point == true) | + select(.value.status == "pending") | + .key as $id | + .value.depends_on as $deps | + if ([$deps[] | . as $d | $GRAPH.nodes[$d].status] | all(. == "completed")) then + $id + else + empty + end +' 2>/dev/null | while IFS= read -r conv_id; do + [ -z "$conv_id" ] && continue + + SUBJECT=$(echo "$GRAPH" | jq -r --arg id "$conv_id" '.nodes[$id].subject') + DEPS=$(echo "$GRAPH" | jq -r --arg id "$conv_id" '.nodes[$id].depends_on[]') + + echo "Integration checkpoint reached: Task $conv_id ($SUBJECT)" >&2 + echo " All upstream tasks completed:" >&2 + for dep_id in $DEPS; do + DEP_OWNER=$(echo "$GRAPH" | jq -r --arg id "$dep_id" '.nodes[$id].owner') + echo " $dep_id ($DEP_OWNER)" >&2 + done + echo " These streams produced independent changes that must integrate at $conv_id." >&2 + echo " Recommend: verify interface compatibility before $conv_id starts." >&2 + + # List output files from upstream tasks + OUTPUT_FILES="" + for dep_id in $DEPS; do + FILES=$(echo "$GRAPH" | jq -r --arg id "$dep_id" '.nodes[$id].output_files[]' 2>/dev/null) + [ -n "$FILES" ] && OUTPUT_FILES="${OUTPUT_FILES}${FILES}\n" + done + if [ -n "$OUTPUT_FILES" ]; then + echo " Shared interfaces: check these output files for contract alignment:" >&2 + printf "$OUTPUT_FILES" | while IFS= read -r f; do + [ -n "$f" ] && echo " - $f" >&2 + done + fi +done + +exit 0 +``` + +- [ ] **Step 4: Make executable** + +```bash +chmod +x scripts/check-integration-point.sh +``` + +- [ ] **Step 5: Run tests — verify they pass** + +```bash +bash tests/hooks/test-check-integration-point.sh +``` + +Expected: all 6 tests pass + +- [ ] **Step 6: Commit** + +```bash +git add scripts/check-integration-point.sh tests/hooks/test-check-integration-point.sh +git commit -m "feat: add check-integration-point.sh hook with convergence detection and tests" +``` + +### Task 5: Register hooks in hooks.json + +**Files:** +- Modify: `hooks/hooks.json` + +- [ ] **Step 1: Add 3 new hook entries** + +Add to the `TaskCompleted` array (after the existing `verify-task-complete.sh` entry): +```json + { + "hooks": [ + { + "type": "command", + "command": "${CLAUDE_PLUGIN_ROOT}/scripts/compute-critical-path.sh", + "timeout": 15 + } + ] + }, + { + "hooks": [ + { + "type": "command", + "command": "${CLAUDE_PLUGIN_ROOT}/scripts/check-integration-point.sh", + "timeout": 15 + } + ] + } +``` + +Add to the `SessionStart` array (after the existing `recover-context.sh` entry): +```json + { + "hooks": [ + { + "type": "command", + "command": "${CLAUDE_PLUGIN_ROOT}/scripts/detect-resume.sh", + "timeout": 15 + } + ] + } +``` + +Note: the new `SessionStart` entry has **no matcher** (fires on all session starts), unlike the existing `compact` matcher entry. + +- [ ] **Step 2: Validate JSON** + +```bash +jq . hooks/hooks.json +``` + +Expected: valid JSON output + +- [ ] **Step 3: Run full test suite** + +```bash +bash tests/run-tests.sh +``` + +Expected: all existing + new tests pass + +- [ ] **Step 4: Commit** + +```bash +git add hooks/hooks.json +git commit -m "feat: register 3 new DAG hook entries in hooks.json" +``` + +## Chunk 2: Documentation Changes + +### Task 6: Update `docs/workspace-templates.md` + +**Files:** +- Modify: `docs/workspace-templates.md` + +- [ ] **Step 1: Add `task-graph.json` section** + +After the `### file-locks.json` section (and before `### events.log`), add the full `task-graph.json` section from the spec: schema, field reference, lifecycle, and applicability table. See spec section "Shared Data Structure: task-graph.json" for exact content. + +- [ ] **Step 2: Add Workspace Update Protocol rows** + +Add these rows to the existing "Workspace Update Protocol" table: + +```markdown +| Tasks created | task-graph.json | Initialize full graph with nodes, compute critical path and convergence points | +| Task started | task-graph.json | Node status → `in_progress` | +| Task completed | task-graph.json | Node status → `completed`, set `completed_at` and `output_files`, recompute `critical_path`. Self-check: read back to verify valid JSON. | +| Task blocked | task-graph.json | Node status → `blocked` | +| Re-plan occurs | task-graph.json | Rebuild graph from revised tasks | +``` + +- [ ] **Step 3: Add CP column to `tasks.md` template** + +In all four task tables (In Progress, Blocked, Pending, Completed), add the `CP` column: + +```markdown +| ID | Subject | Owner | Ref | CP | Notes | +|----|---------|-------|-----|----|-------| +``` + +For Blocked and Pending tables (which have Blocked By): + +```markdown +| ID | Subject | Owner | Ref | CP | Blocked By | Notes | +|----|---------|-------|-----|----|-----------|-------| +``` + +- [ ] **Step 4: Commit** + +```bash +git add docs/workspace-templates.md +git commit -m "docs: add task-graph.json schema and CP column to workspace-templates" +``` + +### Task 7: Update `docs/shared-phases.md` + +**Files:** +- Modify: `docs/shared-phases.md` + +- [ ] **Step 1: Add Phase 1b step 5a** + +After the existing step 5 ("Integration points — for each pair of streams, identify where plan tasks reference shared interfaces, contracts, or outputs. These become explicit handoff points in Phase 2."), add: + +```markdown +6. **Mark convergence points** — for each task that depends on 2+ upstream tasks, flag it as a convergence point. These become integration checkpoints during Phase 4 — the `check-integration-point.sh` hook will nudge the lead to verify interface compatibility when all upstream tasks complete. Include convergence points in the Phase 2 presentation. +``` + +Renumber existing steps 6-7 to 7-8. + +- [ ] **Step 2: Add critical path and convergence to Phase 2 plan template** + +After the `Task breakdown:` section in the Phase 2 template, add: + +```markdown +Critical path: [#X → #Y → #Z] (length: N) + Non-critical (can slip without affecting total time): [#A, #B] + Integration checkpoints: [#Y (converges #X + #A — verify interface compatibility)] +``` + +Add to the Phase 2 self-check list (after item 3): + +```markdown +4. "Have I identified the critical path? Is it displayed in the plan? Are convergence points marked?" +``` + +- [ ] **Step 3: Add Phase 3 step 1a (resume detection)** + +Before the existing step 1 ("Check for existing team"), add: + +```markdown +1a. **Check for resumable workspace** — if the `detect-resume.sh` hook surfaced a resumable workspace at session start, present the resume option to the user: +``` + +Include the full resume protocol from the spec (options, if-resuming behavior, if-starting-fresh behavior). + +- [ ] **Step 4: Add Phase 3 step 4a (create task-graph.json)** + +After the existing step 4 (Create ALL tasks upfront), add: + +```markdown +4a. **Create `task-graph.json`** — immediately after creating all tasks, generate `.agent-team/{team-name}/task-graph.json` with the full dependency graph. Compute the initial critical path (longest chain, tie-break by lowest task ID) and mark convergence points (nodes with 2+ dependencies). Validate the graph is acyclic — if a cycle is detected, fix it before proceeding (see Circular Dependency Detection in coordination-advanced.md). Update `tasks.md` with ★ markers on critical-path tasks and convergence notes. See [workspace-templates.md](workspace-templates.md#task-graphjson) for schema. +``` + +- [ ] **Step 5: Add Phase 4 updates** + +Add `task-graph.json` update instruction to the COMPLETED row in the Lead Processing Rules table: + +```markdown +| COMPLETED | Update `tasks.md` status to `completed`, add file list and notes. Update `task-graph.json`: set node status to `completed`, record `completed_at` and `output_files`. Self-check: read `task-graph.json` back to verify valid JSON. Check: does this unblock other tasks? If yes, message the dependent teammate | +``` + +Add integration checkpoint processing row: + +```markdown +| (hook: integration checkpoint) | Read the nudge from `check-integration-point.sh`. Before unblocking the convergence task, verify interface compatibility between upstream outputs. If compatible, message the convergence task owner to proceed. If unclear, log in `issues.md` as medium severity. Log checkpoint in `progress.md` Decision Log. | +``` + +Add new subsection after "Communication Protocol": + +```markdown +### Critical Path Awareness + +The critical path determines total execution time. The `compute-critical-path.sh` hook outputs the remaining critical path after every task completion. Use it to prioritize: + +- **BLOCKED on critical path** → resolve immediately (highest-priority coordination action) +- **BLOCKED on non-critical path** → resolve normally (slippage has slack) +- **Teammate idle on critical path** → reassign work to keep the critical chain moving +- **Teammate idle on non-critical path** → lower priority, consider assigning critical-path support tasks + +After every task completion, read the hook output. If the critical path shifted, update `task-graph.json` and the ★ markers in `tasks.md`. +``` + +- [ ] **Step 6: Update Contents/TOC** + +Add anchor links for the new sections to the Contents list at the top of the file. + +- [ ] **Step 7: Commit** + +```bash +git add docs/shared-phases.md +git commit -m "docs: add critical path, resume detection, and convergence points to shared phases" +``` + +### Task 8: Update `docs/coordination-patterns.md` + +**Files:** +- Modify: `docs/coordination-patterns.md` + +- [ ] **Step 1: Add "Resume from Existing Workspace" section** + +After the "Setup Failures" section, add the full resume protocol from the spec: Valid Completed Tasks, Stale Completed Tasks, Remaining Tasks, Archive Protocol. + +- [ ] **Step 2: Add "Integration Checkpoint Response" section** + +After the "Direct Handoff" section, add the full integration checkpoint response protocol from the spec. + +- [ ] **Step 3: Update Contents/TOC** + +Add new section links. + +- [ ] **Step 4: Commit** + +```bash +git add docs/coordination-patterns.md +git commit -m "docs: add resume and integration checkpoint patterns to coordination-patterns" +``` + +### Task 9: Update `docs/coordination-advanced.md` + +**Files:** +- Modify: `docs/coordination-advanced.md` + +- [ ] **Step 1: Add critical-path awareness to Deadline Escalation** + +In the "Deadline Escalation" section, add to the protocol step 2: + +```markdown +When checking stalled tasks, prioritize **critical-path tasks** (marked in `task-graph.json`). A stalled critical-path task directly delays total completion. Adjust escalation urgency: +- Critical-path task stalled → skip Nudge, go directly to **Warn** +- Non-critical task stalled → follow normal Nudge → Warn → Escalate ladder +``` + +- [ ] **Step 2: Commit** + +```bash +git add docs/coordination-advanced.md +git commit -m "docs: integrate critical path awareness into deadline escalation" +``` + +### Task 10: Update `docs/report-format.md` + +**Files:** +- Modify: `docs/report-format.md` + +- [ ] **Step 1: Add metrics to Team Metrics table** + +Add these rows: + +```markdown +| Critical path length | {initial} → {final} (shifted {count} times) | +| Integration checkpoints | {count} ({passed}/{flagged}) | +| Resumed tasks | {count valid}/{count stale}/{count remaining} (or "N/A — fresh start") | +``` + +- [ ] **Step 2: Add CP column to Task Ledger** + +Update the Task Ledger table in Full Audit Trail: + +```markdown +| ID | Subject | Owner | Status | CP | Notes | +|----|---------|-------|--------|----|-------| +``` + +- [ ] **Step 3: Commit** + +```bash +git add docs/report-format.md +git commit -m "docs: add critical path metrics and CP column to report format" +``` + +### Task 11: Update all 5 archetype SKILL.md files + +**Files:** +- Modify: `skills/agent-team/SKILL.md` +- Modify: `skills/agent-implement/SKILL.md` +- Modify: `skills/agent-research/SKILL.md` +- Modify: `skills/agent-audit/SKILL.md` +- Modify: `skills/agent-plan/SKILL.md` + +- [ ] **Step 1: Add step 4a reference to all 5 skills** + +In each skill's Phase 3 Override section, add after the shared Phase 3 reference: + +```markdown +After shared Phase 3 step 4 (create tasks), execute step 4a: create `task-graph.json` with initial critical path and convergence points. See [shared-phases.md](../../docs/shared-phases.md) and [workspace-templates.md](../../docs/workspace-templates.md#task-graphjson). +``` + +- [ ] **Step 2: Add convergence-point awareness to agent-implement completion gate** + +In `skills/agent-implement/SKILL.md`, update Completion Gate check #4 (Integration): + +```markdown +| 4 | **Integration** | Assign teammate: "Verify cross-module connections". If any convergence points in `task-graph.json` were flagged during Phase 4, verify they were resolved. | Cross-teammate outputs connect, flagged convergence points resolved | Create integration fix task | +``` + +- [ ] **Step 3: Commit** + +```bash +git add skills/*/SKILL.md +git commit -m "docs: add task-graph.json step 4a reference to all archetype skills" +``` + +## Chunk 3: User-Facing Docs + Release + +### Task 12: Update README.md + +**Files:** +- Modify: `README.md` + +- [ ] **Step 1: Add `task-graph.json` to workspace file tree** + +```markdown +.agent-team/0304-refactor-auth/ +├── progress.md # Team status, members, decisions, handoffs +├── tasks.md # Task ledger with status tracking +├── issues.md # Issue tracker with severity and resolution +├── file-locks.json # File ownership map (teammate -> files/directories) +├── task-graph.json # DAG: task dependencies, critical path, convergence points +├── events.log # Structured JSON event log for post-mortem analysis +└── report.md # Final report (generated at completion) +``` + +- [ ] **Step 2: Add 3 new hooks to Hooks section** + +After the existing SubagentStart/SubagentStop entry: + +```markdown +### ComputeCriticalPath (TaskCompleted) + +Recomputes and displays the critical path after each task completion: +- Reads `task-graph.json` for the dependency graph +- Outputs remaining critical path and identifies blocked critical tasks +- Informational only — always allows task completion + +### DetectResume (SessionStart) + +Detects resumable workspaces at session start: +- Scans for incomplete `task-graph.json` files in `.agent-team/` +- Validates completed task output files via git timestamps (valid/stale/missing) +- Outputs resume context with options to resume or start fresh + +### CheckIntegrationPoint (TaskCompleted) + +Detects when convergence points become fully unblocked: +- Checks if all upstream tasks of a convergence point are completed +- Nudges the lead to verify interface compatibility before downstream task starts +- Informational only — silent when no convergence point is ready +``` + +- [ ] **Step 3: Add 3 new scripts to Plugin Structure tree** + +```markdown +├── scripts/ +│ ├── verify-task-complete.sh +│ ├── check-teammate-idle.sh +│ ├── recover-context.sh +│ ├── check-file-ownership.sh +│ ├── track-teammate-lifecycle.sh +│ ├── setup-worktree.sh +│ ├── merge-worktrees.sh +│ ├── compute-critical-path.sh # NEW — critical path hook +│ ├── detect-resume.sh # NEW — resume detection hook +│ └── check-integration-point.sh # NEW — integration checkpoint hook +``` + +- [ ] **Step 4: Commit** + +```bash +git add README.md +git commit -m "docs: add DAG hooks and task-graph.json to README" +``` + +### Task 13: Update CLAUDE.md + +**Files:** +- Modify: `CLAUDE.md` + +- [ ] **Step 1: Update File Ownership table** + +Add row for `task-graph.json`: + +```markdown +| `docs/workspace-templates.md` | Workspace file templates + `task-graph.json` schema | Update when adding new workspace files or changing DAG schema | +``` + +Update hooks.json row: + +```markdown +| `hooks/hooks.json` | Hook registration (9 hook entries) | Update timeout values, add new hooks, or update hook command paths | +``` + +Update scripts row: + +```markdown +| `scripts/*.sh` | Hook enforcement logic (12 scripts) | Written in bash (`#!/bin/bash`), degrade gracefully without `jq` | +``` + +- [ ] **Step 2: Add 3 new verification scenarios to Verify Hooks** + +```markdown +7. **ComputeCriticalPath** — complete a task and check stderr for critical path update +8. **DetectResume** — start a new session with an incomplete workspace and check stdout for resume context +9. **CheckIntegrationPoint** — complete both upstream tasks of a convergence point and check stderr for integration nudge +``` + +- [ ] **Step 3: Commit** + +```bash +git add CLAUDE.md +git commit -m "docs: update CLAUDE.md file ownership and hook counts for v2.6.0" +``` + +### Task 14: Update existing test assertions + +**Files:** +- Modify: `tests/structure/test-doc-references.sh` + +- [ ] **Step 1: Add `task-graph.json` reference assertion** + +After the existing doc reference tests, add: + +```bash +# --- Test: workspace-templates.md references task-graph.json --- +TASK_GRAPH_REF=$(grep -c 'task-graph.json' docs/workspace-templates.md) +assert_true "workspace-templates.md references task-graph.json" "[ $TASK_GRAPH_REF -gt 0 ]" + +# --- Test: All SKILL.md files reference step 4a --- +for SKILL_MD in skills/*/SKILL.md; do + SKILL_NAME=$(basename "$(dirname "$SKILL_MD")") + STEP4A_REF=$(grep -c 'step 4a\|task-graph.json' "$SKILL_MD") + assert_true "$SKILL_NAME: SKILL.md references step 4a or task-graph.json" "[ $STEP4A_REF -gt 0 ]" +done + +# --- Test: New scripts referenced in docs --- +for script in compute-critical-path.sh detect-resume.sh check-integration-point.sh; do + SCRIPT_REF=$(grep -rl "$script" docs/ | wc -l | tr -d ' ') + assert_true "$script referenced in docs/" "[ $SCRIPT_REF -gt 0 ]" +done +``` + +- [ ] **Step 2: Commit** + +```bash +git add tests/structure/test-doc-references.sh +git commit -m "test: add task-graph.json and DAG script reference assertions" +``` + +### Task 15: Update CHANGELOG.md + bump version + +**Files:** +- Modify: `CHANGELOG.md` +- Modify: `.claude-plugin/plugin.json` +- Modify: `.claude-plugin/marketplace.json` + +- [ ] **Step 1: Add v2.6.0 entry to CHANGELOG.md** + +At the top (after the header), add: + +```markdown +## [2.6.0] - 2026-03-20 + +### Added +- **`task-graph.json` workspace file** — centralized DAG with task dependencies, critical path, and convergence points. Created in Phase 3, maintained by lead, read by 3 new hook scripts +- **`compute-critical-path.sh` hook** (TaskCompleted) — recomputes and displays remaining critical path after each task completion, warns about blocked critical tasks +- **`detect-resume.sh` hook** (SessionStart) — detects resumable workspaces with smart staleness validation via git timestamps (valid/stale/missing output files) +- **`check-integration-point.sh` hook** (TaskCompleted) — detects when convergence points (diamond dependencies) become fully unblocked, nudges lead to verify interface compatibility +- **Critical Path Awareness** in Phase 4 — lead prioritizes critical-path blockers over non-critical work +- **Resume from Existing Workspace** coordination pattern — valid/stale/remaining protocol with archive option +- **Integration Checkpoint Response** coordination pattern — lead response protocol for convergence nudges +- **CP column** in `tasks.md` — ★ marks critical path tasks, convergence notes in the Notes column + +### Changed +- Phase 1b gains convergence point marking (step 5a/6 after integration points) +- Phase 2 gains critical path display and integration checkpoint preview +- Phase 3 gains resume detection (step 1a) and `task-graph.json` creation (step 4a) +- Phase 4 gains critical-path-weighted prioritization and integration checkpoint processing +- Deadline Escalation gains critical-path acceleration (skip Nudge, go to Warn) +- Report gains critical path metrics (initial → final length, shift count) and integration checkpoint counts +- All 5 archetype SKILL.md files reference step 4a +- `agent-implement` completion gate check #4 gains convergence-point awareness +``` + +- [ ] **Step 2: Bump version in plugin.json** + +Change `"version": "2.5.1"` to `"version": "2.6.0"`. + +- [ ] **Step 3: Bump version in marketplace.json** + +Change `"version": "2.5.1"` to `"version": "2.6.0"`. + +- [ ] **Step 4: Run full test suite** + +```bash +bash tests/run-tests.sh +``` + +Expected: 12 test files, all passing + +- [ ] **Step 5: Commit** + +```bash +git add CHANGELOG.md .claude-plugin/plugin.json .claude-plugin/marketplace.json +git commit -m "chore: bump version to 2.6.0" +``` + +- [ ] **Step 6: Update spec status** + +Change spec status from `DRAFT` to `IMPLEMENTED`: + +```bash +sed -i '' 's/^**Status**: DRAFT/**Status**: IMPLEMENTED/' docs/specs/2026-03-20-dag-aligned-improvements-design.md +git add docs/specs/2026-03-20-dag-aligned-improvements-design.md +git commit -m "docs: mark DAG improvements spec as IMPLEMENTED" +``` From 71a2caaf5b45991447dd370c2089266aa2d9a1c5 Mon Sep 17 00:00:00 2001 From: ducdmdev Date: Fri, 20 Mar 2026 09:46:50 +0700 Subject: [PATCH 03/22] test: add task-graph.json mock helper and stdout capture for DAG hooks --- tests/lib/test-helpers.sh | 99 +++++++++++++++++++++++++++++++++++++++ 1 file changed, 99 insertions(+) diff --git a/tests/lib/test-helpers.sh b/tests/lib/test-helpers.sh index 8d8e3ad..2306a9d 100755 --- a/tests/lib/test-helpers.sh +++ b/tests/lib/test-helpers.sh @@ -112,6 +112,73 @@ setup_mock_git_repo() { ) } +# --- Mock task-graph.json --- +# Creates a task-graph.json inside an existing mock workspace. +# Usage: setup_mock_task_graph "my-team" '{...json...}' +# If no JSON provided, creates a default 4-task graph (2 parallel + 1 convergence + 1 review) +setup_mock_task_graph() { + local team_name="$1" + local custom_json="${2:-}" + local graph_file="$TEST_TEMP_DIR/.agent-team/$team_name/task-graph.json" + + if [ -n "$custom_json" ]; then + echo "$custom_json" > "$graph_file" + return + fi + + cat > "$graph_file" <<'GRAPH' +{ + "team": "test", + "created": "2026-03-20T10:00:00Z", + "updated": "2026-03-20T10:00:00Z", + "nodes": { + "#1": { + "subject": "Implement auth", + "owner": "impl-1", + "status": "pending", + "depends_on": [], + "completed_at": null, + "output_files": ["src/auth.ts"], + "critical_path": true, + "convergence_point": false + }, + "#2": { + "subject": "Implement session", + "owner": "impl-2", + "status": "pending", + "depends_on": [], + "completed_at": null, + "output_files": ["src/session.ts"], + "critical_path": false, + "convergence_point": false + }, + "#3": { + "subject": "Integrate middleware", + "owner": "impl-1", + "status": "pending", + "depends_on": ["#1", "#2"], + "completed_at": null, + "output_files": ["src/middleware.ts"], + "critical_path": true, + "convergence_point": true + }, + "#4": { + "subject": "Review all", + "owner": "reviewer", + "status": "pending", + "depends_on": ["#3"], + "completed_at": null, + "output_files": [], + "critical_path": true, + "convergence_point": false + } + }, + "critical_path": ["#1", "#3", "#4"], + "critical_path_length": 3 +} +GRAPH +} + # --- Run a hook script --- # Feeds JSON via stdin, captures exit code and stderr. # Usage: run_hook "$script" "$json_input" @@ -126,6 +193,23 @@ run_hook() { HOOK_EXIT=$? } +# --- Run hook capturing stdout separately --- +# Like run_hook but also captures stdout (needed for detect-resume.sh which outputs to stdout). +# Usage: run_hook_full "$script" "$json_input" +# Sets: HOOK_EXIT, HOOK_STDOUT, HOOK_STDERR +HOOK_STDOUT="" + +run_hook_full() { + local script="$1" + local input="$2" + local stdout_file + stdout_file=$(mktemp "${TMPDIR:-/tmp}/hook-stdout.XXXXXX") + HOOK_STDERR=$(echo "$input" | bash "$script" 2>&1 1>"$stdout_file") + HOOK_EXIT=$? + HOOK_STDOUT=$(cat "$stdout_file") + rm -f "$stdout_file" +} + # --- Assertions --- assert_exit_code() { @@ -157,6 +241,21 @@ assert_stderr_contains() { fi } +assert_stdout_contains() { + local pattern="$1" + local stdout_output="$2" + local test_name="$3" + TESTS_TOTAL=$((TESTS_TOTAL + 1)) + if echo "$stdout_output" | grep -qi "$pattern"; then + TESTS_PASSED=$((TESTS_PASSED + 1)) + printf " ${GREEN}PASS${RESET} %s\n" "$test_name" + else + TESTS_FAILED=$((TESTS_FAILED + 1)) + printf " ${RED}FAIL${RESET} %s (stdout missing pattern '%s')\n" "$test_name" "$pattern" + printf " stdout was: %s\n" "$stdout_output" + fi +} + assert_true() { local test_name="$1" local condition="$2" From af3d113c7549f190bd236fa23597bb3ff128b55a Mon Sep 17 00:00:00 2001 From: ducdmdev Date: Fri, 20 Mar 2026 09:52:46 +0700 Subject: [PATCH 04/22] feat: add compute-critical-path.sh hook with tests --- scripts/compute-critical-path.sh | 138 ++++++++++++++++++++ tests/hooks/test-compute-critical-path.sh | 148 ++++++++++++++++++++++ 2 files changed, 286 insertions(+) create mode 100755 scripts/compute-critical-path.sh create mode 100644 tests/hooks/test-compute-critical-path.sh diff --git a/scripts/compute-critical-path.sh b/scripts/compute-critical-path.sh new file mode 100755 index 0000000..d339dfd --- /dev/null +++ b/scripts/compute-critical-path.sh @@ -0,0 +1,138 @@ +#!/bin/bash +# Hook: TaskCompleted +# Recomputes and displays the critical path from task-graph.json. +# Informational only — always exits 0. + +# Graceful jq fallback +if ! command -v jq &>/dev/null; then + exit 0 +fi + +INPUT=$(cat) +CWD=$(echo "$INPUT" | jq -r '.cwd // empty') +TEAM_NAME=$(echo "$INPUT" | jq -r '.team_name // empty') + +# Need both cwd and team_name to locate task-graph.json +if [ -z "$CWD" ] || [ -z "$TEAM_NAME" ]; then + exit 0 +fi + +# Resolve workspace path (with -fix suffix fallback for remediation teams) +GRAPH_FILE="$CWD/.agent-team/$TEAM_NAME/task-graph.json" +if [ ! -f "$GRAPH_FILE" ]; then + BASE_NAME="${TEAM_NAME%-fix}" + if [ "$BASE_NAME" != "$TEAM_NAME" ] && [ -f "$CWD/.agent-team/$BASE_NAME/task-graph.json" ]; then + GRAPH_FILE="$CWD/.agent-team/$BASE_NAME/task-graph.json" + else + exit 0 + fi +fi + +# Parse JSON — warn if malformed +GRAPH=$(jq '.' "$GRAPH_FILE" 2>/dev/null) +if [ -z "$GRAPH" ]; then + echo "Warning: task-graph.json exists but failed to parse. Critical path hooks disabled until JSON is fixed." >&2 + exit 0 +fi + +# Count remaining (non-completed) nodes +REMAINING=$(echo "$GRAPH" | jq '[.nodes | to_entries[] | select(.value.status != "completed")] | length') + +if [ "$REMAINING" -eq 0 ]; then + echo "No critical path — all remaining tasks can run in parallel." >&2 + exit 0 +fi + +# DFS longest path computation via jq +# For each remaining node, compute depth = 1 + max(depth of remaining dependencies) +# Cycle guard: track visited nodes +# Key jq scoping note: inside recursive calls, we must capture the dep ID with +# `. as $dep_id | $root | depth($dep_id; ...)` to preserve the graph as `.` context. +CRITICAL_PATH=$(echo "$GRAPH" | jq -r ' + def depth(id; visited): + if (visited | index(id)) then 0 + elif (.nodes[id].status == "completed") then 0 + elif (.nodes[id].depends_on | length == 0) then 1 + else + . as $g | + [.nodes[id].depends_on[] | select($g.nodes[.].status != "completed") | . as $dep_id | $g | depth($dep_id; visited + [id])] | max + 1 + end; + + . as $root | + [.nodes | to_entries[] | select(.value.status != "completed") | + .key as $k | $root | {id: $k, depth: depth($k; [])} + ] | sort_by(-.depth, .id) | + if length == 0 then empty + else + .[0] as $deepest | + def trace_path(id): + if ($root.nodes[id].status == "completed") then [] + else + [$root.nodes[id].depends_on[] | select($root.nodes[.].status != "completed")] as $remaining_deps | + if ($remaining_deps | length) == 0 then [id] + else + ([$remaining_deps[] | . as $dep_id | $root | {id: $dep_id, depth: depth($dep_id; [])}] | sort_by(-.depth, .id) | .[0].id) as $next | + trace_path($next) + [id] + end + end; + $root | trace_path($deepest.id) | join(" → ") + end +' 2>/dev/null) + +# Get the previously recorded critical path +OLD_CP=$(echo "$GRAPH" | jq -r '.critical_path | join(" → ")') +CP_LENGTH=$(echo "$CRITICAL_PATH" | tr '→' '\n' | sed 's/ //g' | grep -c '.') + +# Check if any completed node was on the old critical path +WAS_ON_CP=false +COMPLETED_NODES=$(echo "$GRAPH" | jq -r '[.nodes | to_entries[] | select(.value.status == "completed") | .key] | .[]') +OLD_CP_NODES=$(echo "$GRAPH" | jq -r '.critical_path[]') +for node in $COMPLETED_NODES; do + if echo "$OLD_CP_NODES" | grep -q "^${node}$"; then + WAS_ON_CP=true + break + fi +done + +# Find next critical task details +NEXT_CRITICAL=$(echo "$CRITICAL_PATH" | cut -d'→' -f1 | sed 's/ //g') +if [ -n "$NEXT_CRITICAL" ]; then + NEXT_SUBJECT=$(echo "$GRAPH" | jq -r --arg id "$NEXT_CRITICAL" '.nodes[$id].subject // "unknown"') + NEXT_OWNER=$(echo "$GRAPH" | jq -r --arg id "$NEXT_CRITICAL" '.nodes[$id].owner // "unassigned"') + NEXT_STATUS=$(echo "$GRAPH" | jq -r --arg id "$NEXT_CRITICAL" '.nodes[$id].status // "unknown"') +fi + +if [ "$WAS_ON_CP" = true ]; then + echo "Critical path update: completed task was on critical path." >&2 + echo "Remaining critical path: $CRITICAL_PATH (length: $CP_LENGTH)" >&2 + if [ -n "$NEXT_CRITICAL" ]; then + echo "Next critical task: $NEXT_CRITICAL $NEXT_SUBJECT (owner: $NEXT_OWNER, status: $NEXT_STATUS)" >&2 + if [ "$NEXT_STATUS" = "blocked" ]; then + BLOCKED_BY=$(echo "$GRAPH" | jq -r --arg id "$NEXT_CRITICAL" ' + . as $g | + [.nodes[$id].depends_on[] | select($g.nodes[.].status != "completed")] | join(", ") + ' 2>/dev/null) + echo "Warning: Critical task $NEXT_CRITICAL is blocked — resolve blocker(s) ${BLOCKED_BY:-unknown} to maintain throughput." >&2 + fi + fi +else + echo "Task completed (not on critical path). Critical path unchanged: $OLD_CP (length: $(echo "$GRAPH" | jq '.critical_path_length'))" >&2 +fi + +# Check for any blocked tasks on the computed critical path +if [ -n "$CRITICAL_PATH" ]; then + CP_NODES=$(echo "$CRITICAL_PATH" | tr '→' '\n' | sed 's/ //g' | grep '.') + for cp_node in $CP_NODES; do + NODE_STATUS=$(echo "$GRAPH" | jq -r --arg id "$cp_node" '.nodes[$id].status // "unknown"') + if [ "$NODE_STATUS" = "blocked" ]; then + BLOCKED_BY=$(echo "$GRAPH" | jq -r --arg id "$cp_node" ' + . as $g | + [.nodes[$id].depends_on[] | select($g.nodes[.].status != "completed")] | join(", ") + ' 2>/dev/null) + echo "Warning: Critical task $cp_node is blocked — resolve blocker(s) ${BLOCKED_BY:-unknown} to maintain throughput." >&2 + break + fi + done +fi + +exit 0 diff --git a/tests/hooks/test-compute-critical-path.sh b/tests/hooks/test-compute-critical-path.sh new file mode 100644 index 0000000..e4742aa --- /dev/null +++ b/tests/hooks/test-compute-critical-path.sh @@ -0,0 +1,148 @@ +#!/bin/bash +# Tests for scripts/compute-critical-path.sh (TaskCompleted hook — critical path) + +source "$(dirname "$0")/../lib/test-helpers.sh" + +HOOK="$PROJECT_ROOT/scripts/compute-critical-path.sh" + +echo "Critical path hook tests" +echo "========================" + +if ! command -v jq &>/dev/null; then + printf " ${YELLOW}SKIP${RESET} all — jq not installed\n" + exit 0 +fi + +# --- Test 1: No task-graph.json exits 0 silently --- +setup_temp_dir +cd "$TEST_TEMP_DIR" +setup_mock_workspace "test" +run_hook "$HOOK" '{"cwd":"'"$TEST_TEMP_DIR"'","team_name":"test"}' +assert_exit_code 0 "$HOOK_EXIT" "1: No task-graph.json exits 0 silently" +cleanup_temp_dir + +# --- Test 2: Empty input exits 0 --- +setup_temp_dir +cd "$TEST_TEMP_DIR" +run_hook "$HOOK" '{}' +assert_exit_code 0 "$HOOK_EXIT" "2: Empty input exits 0" +cleanup_temp_dir + +# --- Test 3: Critical path task completes — shows remaining path --- +setup_temp_dir +cd "$TEST_TEMP_DIR" +setup_mock_workspace "test" +# Mark #1 as completed in the graph +setup_mock_task_graph "test" "$(cat <<'JSON' +{ + "team": "test", + "created": "2026-03-20T10:00:00Z", + "updated": "2026-03-20T10:30:00Z", + "nodes": { + "#1": {"subject":"Auth","owner":"impl-1","status":"completed","depends_on":[],"completed_at":"2026-03-20T10:30:00Z","output_files":["src/auth.ts"],"critical_path":true,"convergence_point":false}, + "#2": {"subject":"Session","owner":"impl-2","status":"pending","depends_on":[],"completed_at":null,"output_files":["src/session.ts"],"critical_path":false,"convergence_point":false}, + "#3": {"subject":"Middleware","owner":"impl-1","status":"pending","depends_on":["#1","#2"],"completed_at":null,"output_files":["src/mw.ts"],"critical_path":true,"convergence_point":true}, + "#4": {"subject":"Review","owner":"reviewer","status":"pending","depends_on":["#3"],"completed_at":null,"output_files":[],"critical_path":true,"convergence_point":false} + }, + "critical_path": ["#1","#3","#4"], + "critical_path_length": 3 +} +JSON +)" +run_hook "$HOOK" '{"cwd":"'"$TEST_TEMP_DIR"'","team_name":"test"}' +assert_exit_code 0 "$HOOK_EXIT" "3: Critical path task complete exits 0" +assert_stderr_contains "Remaining critical path" "$HOOK_STDERR" "3: Shows remaining critical path" +cleanup_temp_dir + +# --- Test 4: Non-critical task completes — shows unchanged path --- +setup_temp_dir +cd "$TEST_TEMP_DIR" +setup_mock_workspace "test" +setup_mock_task_graph "test" "$(cat <<'JSON' +{ + "team": "test", + "created": "2026-03-20T10:00:00Z", + "updated": "2026-03-20T10:30:00Z", + "nodes": { + "#1": {"subject":"Auth","owner":"impl-1","status":"pending","depends_on":[],"completed_at":null,"output_files":["src/auth.ts"],"critical_path":true,"convergence_point":false}, + "#2": {"subject":"Session","owner":"impl-2","status":"completed","depends_on":[],"completed_at":"2026-03-20T10:30:00Z","output_files":["src/session.ts"],"critical_path":false,"convergence_point":false}, + "#3": {"subject":"Middleware","owner":"impl-1","status":"pending","depends_on":["#1","#2"],"completed_at":null,"output_files":["src/mw.ts"],"critical_path":true,"convergence_point":true} + }, + "critical_path": ["#1","#3"], + "critical_path_length": 2 +} +JSON +)" +run_hook "$HOOK" '{"cwd":"'"$TEST_TEMP_DIR"'","team_name":"test"}' +assert_exit_code 0 "$HOOK_EXIT" "4: Non-critical task complete exits 0" +assert_stderr_contains "not on critical path" "$HOOK_STDERR" "4: Shows not on critical path" +cleanup_temp_dir + +# --- Test 5: All tasks complete — no critical path --- +setup_temp_dir +cd "$TEST_TEMP_DIR" +setup_mock_workspace "test" +setup_mock_task_graph "test" "$(cat <<'JSON' +{ + "team": "test", + "created": "2026-03-20T10:00:00Z", + "updated": "2026-03-20T11:00:00Z", + "nodes": { + "#1": {"subject":"Auth","owner":"impl-1","status":"completed","depends_on":[],"completed_at":"2026-03-20T10:30:00Z","output_files":["src/auth.ts"],"critical_path":false,"convergence_point":false}, + "#2": {"subject":"Session","owner":"impl-2","status":"completed","depends_on":[],"completed_at":"2026-03-20T10:45:00Z","output_files":["src/session.ts"],"critical_path":false,"convergence_point":false} + }, + "critical_path": [], + "critical_path_length": 0 +} +JSON +)" +run_hook "$HOOK" '{"cwd":"'"$TEST_TEMP_DIR"'","team_name":"test"}' +assert_exit_code 0 "$HOOK_EXIT" "5: All complete exits 0" +assert_stderr_contains "all remaining tasks can run in parallel\|No critical path" "$HOOK_STDERR" "5: Shows no critical path" +cleanup_temp_dir + +# --- Test 6: Malformed JSON — warns but exits 0 --- +setup_temp_dir +cd "$TEST_TEMP_DIR" +setup_mock_workspace "test" +echo "NOT VALID JSON" > "$TEST_TEMP_DIR/.agent-team/test/task-graph.json" +run_hook "$HOOK" '{"cwd":"'"$TEST_TEMP_DIR"'","team_name":"test"}' +assert_exit_code 0 "$HOOK_EXIT" "6: Malformed JSON exits 0" +assert_stderr_contains "warning\|parse" "$HOOK_STDERR" "6: Warns about parse failure" +cleanup_temp_dir + +# --- Test 7: Remediation team -fix suffix fallback --- +setup_temp_dir +cd "$TEST_TEMP_DIR" +setup_mock_workspace "test" +setup_mock_task_graph "test" +run_hook "$HOOK" '{"cwd":"'"$TEST_TEMP_DIR"'","team_name":"test-fix"}' +assert_exit_code 0 "$HOOK_EXIT" "7: -fix suffix falls back to base workspace" +cleanup_temp_dir + +# --- Test 8: Blocked critical task warning --- +setup_temp_dir +cd "$TEST_TEMP_DIR" +setup_mock_workspace "test" +setup_mock_task_graph "test" "$(cat <<'JSON' +{ + "team": "test", + "created": "2026-03-20T10:00:00Z", + "updated": "2026-03-20T10:30:00Z", + "nodes": { + "#1": {"subject":"Auth","owner":"impl-1","status":"completed","depends_on":[],"completed_at":"2026-03-20T10:30:00Z","output_files":["src/auth.ts"],"critical_path":true,"convergence_point":false}, + "#2": {"subject":"Session","owner":"impl-2","status":"pending","depends_on":[],"completed_at":null,"output_files":["src/session.ts"],"critical_path":false,"convergence_point":false}, + "#3": {"subject":"Middleware","owner":"impl-1","status":"blocked","depends_on":["#1","#2"],"completed_at":null,"output_files":["src/mw.ts"],"critical_path":true,"convergence_point":true} + }, + "critical_path": ["#1","#3"], + "critical_path_length": 2 +} +JSON +)" +run_hook "$HOOK" '{"cwd":"'"$TEST_TEMP_DIR"'","team_name":"test"}' +assert_exit_code 0 "$HOOK_EXIT" "8: Blocked critical task exits 0" +assert_stderr_contains "blocked" "$HOOK_STDERR" "8: Warns about blocked critical task" +cleanup_temp_dir + +print_summary +exit "$TESTS_FAILED" From 25b081a2807cff5ad8a5068e9f7cfb872cbdc53e Mon Sep 17 00:00:00 2001 From: ducdmdev Date: Fri, 20 Mar 2026 09:54:37 +0700 Subject: [PATCH 05/22] feat: add detect-resume.sh hook with staleness validation and tests --- scripts/detect-resume.sh | 121 ++++++++++++++++++++++++++++++ tests/hooks/test-detect-resume.sh | 120 +++++++++++++++++++++++++++++ 2 files changed, 241 insertions(+) create mode 100755 scripts/detect-resume.sh create mode 100755 tests/hooks/test-detect-resume.sh diff --git a/scripts/detect-resume.sh b/scripts/detect-resume.sh new file mode 100755 index 0000000..304288e --- /dev/null +++ b/scripts/detect-resume.sh @@ -0,0 +1,121 @@ +#!/bin/bash +# Hook: SessionStart (no matcher — fires on all session starts) +# Detects existing workspaces with incomplete tasks and validates staleness. +# Output goes to stdout (injected into conversation context, matching recover-context.sh). +# Always exits 0 (informational only). + +# Graceful jq fallback +if ! command -v jq &>/dev/null; then + exit 0 +fi + +INPUT=$(cat) +CWD=$(echo "$INPUT" | jq -r '.cwd // empty') +CWD="${CWD:-.}" + +# Scan for task-graph.json files +GRAPHS=() +for graph_file in "$CWD"/.agent-team/*/task-graph.json; do + [ -f "$graph_file" ] || continue + GRAPHS+=("$graph_file") +done + +if [ ${#GRAPHS[@]} -eq 0 ]; then + exit 0 +fi + +# Sort by updated timestamp (most recent first) +SORTED_GRAPHS=() +while IFS= read -r line; do + SORTED_GRAPHS+=("$line") +done < <( + for g in "${GRAPHS[@]}"; do + ts=$(jq -r '.updated // .created // "1970-01-01"' "$g" 2>/dev/null) + echo "$ts|$g" + done | sort -r | cut -d'|' -f2 +) + +HAS_OUTPUT=false + +for graph_file in "${SORTED_GRAPHS[@]}"; do + GRAPH=$(jq '.' "$graph_file" 2>/dev/null) + [ -z "$GRAPH" ] && continue + + TEAM=$(echo "$GRAPH" | jq -r '.team // "unknown"') + WORKSPACE_DIR=$(dirname "$graph_file") + + # Count total and completed + TOTAL=$(echo "$GRAPH" | jq '[.nodes | to_entries[]] | length') + COMPLETED=$(echo "$GRAPH" | jq '[.nodes | to_entries[] | select(.value.status == "completed")] | length') + REMAINING=$((TOTAL - COMPLETED)) + + # Skip fully completed workspaces + if [ "$REMAINING" -eq 0 ]; then + continue + fi + + # Validate completed tasks for staleness + VALID_LIST="" + STALE_LIST="" + REMAINING_LIST="" + + while IFS= read -r entry; do + ID=$(echo "$entry" | jq -r '.key') + STATUS=$(echo "$entry" | jq -r '.value.status') + SUBJECT=$(echo "$entry" | jq -r '.value.subject') + + if [ "$STATUS" = "completed" ]; then + COMPLETED_AT=$(echo "$entry" | jq -r '.value.completed_at // empty') + OUTPUT_FILES=$(echo "$entry" | jq -r '.value.output_files[]' 2>/dev/null) + IS_STALE=false + + if [ -n "$OUTPUT_FILES" ] && [ -n "$COMPLETED_AT" ] && command -v git &>/dev/null; then + while IFS= read -r ofile; do + [ -z "$ofile" ] && continue + FULL_PATH="$CWD/$ofile" + if [ -f "$FULL_PATH" ]; then + FILE_DATE=$(cd "$CWD" && git log -1 --format=%cI -- "$ofile" 2>/dev/null) + if [ -n "$FILE_DATE" ] && [[ "$FILE_DATE" > "$COMPLETED_AT" ]]; then + IS_STALE=true + STALE_LIST="${STALE_LIST} Completed (stale): $ID ($SUBJECT) — $ofile modified after completion\n" + break + fi + fi + done <<< "$OUTPUT_FILES" + fi + + if [ "$IS_STALE" = false ]; then + if command -v git &>/dev/null; then + VALID_LIST="${VALID_LIST} Completed (valid): $ID ($SUBJECT) — output files unchanged\n" + else + VALID_LIST="${VALID_LIST} Completed (valid, unverified): $ID ($SUBJECT) — git unavailable\n" + fi + fi + else + REMAINING_LIST="${REMAINING_LIST} Remaining: $ID ($SUBJECT) — status: $STATUS\n" + fi + done < <(echo "$GRAPH" | jq -c '.nodes | to_entries[]') + + # Output resume context to stdout + HAS_OUTPUT=true + REL_PATH="${WORKSPACE_DIR#$CWD/}" + echo "" + echo "Resumable workspace found: $REL_PATH/" + echo " Tasks: $COMPLETED/$TOTAL completed, $REMAINING remaining" + [ -n "$VALID_LIST" ] && printf "$VALID_LIST" + [ -n "$STALE_LIST" ] && printf "$STALE_LIST" + [ -n "$REMAINING_LIST" ] && printf "$REMAINING_LIST" + + # Show remaining critical path if available + CP=$(echo "$GRAPH" | jq -r ' + . as $g | + [.critical_path[] | select(. as $id | $g.nodes[$id].status != "completed")] | join(" → ") + ' 2>/dev/null) + [ -n "$CP" ] && echo " Critical path (remaining): $CP" + + echo "" + echo " To resume: \"resume team $TEAM\"" + echo " To start fresh: proceed normally (existing workspace will be archived)" +done + +exit 0 diff --git a/tests/hooks/test-detect-resume.sh b/tests/hooks/test-detect-resume.sh new file mode 100755 index 0000000..ac3ea7f --- /dev/null +++ b/tests/hooks/test-detect-resume.sh @@ -0,0 +1,120 @@ +#!/bin/bash +# Tests for scripts/detect-resume.sh (SessionStart hook — resume detection) + +source "$(dirname "$0")/../lib/test-helpers.sh" + +HOOK="$PROJECT_ROOT/scripts/detect-resume.sh" + +echo "Resume detection hook tests" +echo "============================" + +if ! command -v jq &>/dev/null; then + printf " ${YELLOW}SKIP${RESET} all — jq not installed\n" + exit 0 +fi + +# --- Test 1: No .agent-team/ directories — silent exit 0 --- +setup_temp_dir +cd "$TEST_TEMP_DIR" +run_hook_full "$HOOK" '{"cwd":"'"$TEST_TEMP_DIR"'"}' +assert_exit_code 0 "$HOOK_EXIT" "1: No workspaces exits 0" +TESTS_TOTAL=$((TESTS_TOTAL + 1)) +if [ -z "$HOOK_STDOUT" ]; then + TESTS_PASSED=$((TESTS_PASSED + 1)) + printf " ${GREEN}PASS${RESET} 1: Silent when no workspaces\n" +else + TESTS_FAILED=$((TESTS_FAILED + 1)) + printf " ${RED}FAIL${RESET} 1: Expected silent, got stdout: %s\n" "$HOOK_STDOUT" +fi +cleanup_temp_dir + +# --- Test 2: All completed workspace — silent --- +setup_temp_dir +cd "$TEST_TEMP_DIR" +setup_mock_workspace "test" +setup_mock_task_graph "test" "$(cat <<'JSON' +{ + "team": "test", + "created": "2026-03-20T10:00:00Z", + "updated": "2026-03-20T11:00:00Z", + "nodes": { + "#1": {"subject":"Auth","owner":"impl-1","status":"completed","depends_on":[],"completed_at":"2026-03-20T10:30:00Z","output_files":["src/auth.ts"],"critical_path":false,"convergence_point":false} + }, + "critical_path": [], + "critical_path_length": 0 +} +JSON +)" +run_hook_full "$HOOK" '{"cwd":"'"$TEST_TEMP_DIR"'"}' +assert_exit_code 0 "$HOOK_EXIT" "2: All completed exits 0" +TESTS_TOTAL=$((TESTS_TOTAL + 1)) +if [ -z "$HOOK_STDOUT" ]; then + TESTS_PASSED=$((TESTS_PASSED + 1)) + printf " ${GREEN}PASS${RESET} 2: Silent when all complete\n" +else + TESTS_FAILED=$((TESTS_FAILED + 1)) + printf " ${RED}FAIL${RESET} 2: Expected silent, got stdout: %s\n" "$HOOK_STDOUT" +fi +cleanup_temp_dir + +# --- Test 3: Incomplete workspace — shows resume context --- +setup_temp_dir +cd "$TEST_TEMP_DIR" +setup_mock_workspace "test" +setup_mock_task_graph "test" +run_hook_full "$HOOK" '{"cwd":"'"$TEST_TEMP_DIR"'"}' +assert_exit_code 0 "$HOOK_EXIT" "3: Incomplete workspace exits 0" +assert_stdout_contains "Resumable workspace" "$HOOK_STDOUT" "3: Shows resumable workspace" +cleanup_temp_dir + +# --- Test 4: Staleness detection — file modified after completion --- +setup_temp_dir +cd "$TEST_TEMP_DIR" +setup_mock_workspace "test" +setup_mock_git_repo "clean" +# Create the output file and commit it +mkdir -p "$TEST_TEMP_DIR/src" +echo "original" > "$TEST_TEMP_DIR/src/auth.ts" +(cd "$TEST_TEMP_DIR" && git add src/auth.ts && git commit -q -m "add auth") +# Create graph with #1 completed in the past +setup_mock_task_graph "test" "$(cat <<'JSON' +{ + "team": "test", + "created": "2026-03-20T10:00:00Z", + "updated": "2026-03-20T10:30:00Z", + "nodes": { + "#1": {"subject":"Auth","owner":"impl-1","status":"completed","depends_on":[],"completed_at":"2026-01-01T00:00:00Z","output_files":["src/auth.ts"],"critical_path":true,"convergence_point":false}, + "#2": {"subject":"Session","owner":"impl-2","status":"pending","depends_on":[],"completed_at":null,"output_files":["src/session.ts"],"critical_path":false,"convergence_point":false} + }, + "critical_path": ["#1"], + "critical_path_length": 1 +} +JSON +)" +# Modify the file AFTER the completed_at timestamp +sleep 1 +echo "modified" > "$TEST_TEMP_DIR/src/auth.ts" +(cd "$TEST_TEMP_DIR" && git add src/auth.ts && git commit -q -m "modify auth") +run_hook_full "$HOOK" '{"cwd":"'"$TEST_TEMP_DIR"'"}' +assert_exit_code 0 "$HOOK_EXIT" "4: Stale detection exits 0" +assert_stdout_contains "stale" "$HOOK_STDOUT" "4: Detects stale output file" +cleanup_temp_dir + +# --- Test 5: Empty cwd falls back to current dir --- +setup_temp_dir +cd "$TEST_TEMP_DIR" +setup_mock_workspace "test" +setup_mock_task_graph "test" +run_hook_full "$HOOK" '{"cwd":""}' +assert_exit_code 0 "$HOOK_EXIT" "5: Empty cwd exits 0" +cleanup_temp_dir + +# --- Test 6: Empty input exits 0 --- +setup_temp_dir +cd "$TEST_TEMP_DIR" +run_hook_full "$HOOK" '{}' +assert_exit_code 0 "$HOOK_EXIT" "6: Empty input exits 0" +cleanup_temp_dir + +print_summary +exit "$TESTS_FAILED" From 7dc4f5345d6f19296c0663d71344e2ce9c6108ed Mon Sep 17 00:00:00 2001 From: ducdmdev Date: Fri, 20 Mar 2026 09:56:20 +0700 Subject: [PATCH 06/22] feat: add check-integration-point.sh hook with convergence detection and tests --- scripts/check-integration-point.sh | 96 +++++++++++++ tests/hooks/test-check-integration-point.sh | 150 ++++++++++++++++++++ 2 files changed, 246 insertions(+) create mode 100755 scripts/check-integration-point.sh create mode 100644 tests/hooks/test-check-integration-point.sh diff --git a/scripts/check-integration-point.sh b/scripts/check-integration-point.sh new file mode 100755 index 0000000..a5c3c41 --- /dev/null +++ b/scripts/check-integration-point.sh @@ -0,0 +1,96 @@ +#!/bin/bash +# Hook: TaskCompleted +# Detects when all upstream tasks of a convergence point complete. +# Nudges the lead to verify interface compatibility. +# Informational only — always exits 0. + +# Graceful jq fallback +if ! command -v jq &>/dev/null; then + exit 0 +fi + +INPUT=$(cat) +CWD=$(echo "$INPUT" | jq -r '.cwd // empty') +TEAM_NAME=$(echo "$INPUT" | jq -r '.team_name // empty') + +if [ -z "$CWD" ] || [ -z "$TEAM_NAME" ]; then + exit 0 +fi + +# Resolve workspace path (with -fix suffix fallback) +GRAPH_FILE="$CWD/.agent-team/$TEAM_NAME/task-graph.json" +if [ ! -f "$GRAPH_FILE" ]; then + BASE_NAME="${TEAM_NAME%-fix}" + if [ "$BASE_NAME" != "$TEAM_NAME" ] && [ -f "$CWD/.agent-team/$BASE_NAME/task-graph.json" ]; then + GRAPH_FILE="$CWD/.agent-team/$BASE_NAME/task-graph.json" + else + exit 0 + fi +fi + +# Parse JSON — warn if malformed +GRAPH=$(jq '.' "$GRAPH_FILE" 2>/dev/null) +if [ -z "$GRAPH" ]; then + echo "Warning: task-graph.json exists but failed to parse. Integration checkpoint hooks disabled until JSON is fixed." >&2 + exit 0 +fi + +# Find convergence points (convergence_point == true, status == pending) +CONV_IDS=$(echo "$GRAPH" | jq -r ' + .nodes | to_entries[] | + select(.value.convergence_point == true) | + select(.value.status == "pending") | + .key +' 2>/dev/null) + +[ -z "$CONV_IDS" ] && exit 0 + +# For each convergence point, check if ALL depends_on nodes are completed +for conv_id in $CONV_IDS; do + # Get depends_on list for this convergence node + DEPS=$(echo "$GRAPH" | jq -r --arg id "$conv_id" '.nodes[$id].depends_on[]' 2>/dev/null) + [ -z "$DEPS" ] && continue + + # Check if all dependencies are completed + ALL_DONE=true + for dep_id in $DEPS; do + DEP_STATUS=$(echo "$GRAPH" | jq -r --arg id "$dep_id" '.nodes[$id].status // "unknown"' 2>/dev/null) + if [ "$DEP_STATUS" != "completed" ]; then + ALL_DONE=false + break + fi + done + + [ "$ALL_DONE" = false ] && continue + + # All deps completed and convergence node is pending — emit nudge + SUBJECT=$(echo "$GRAPH" | jq -r --arg id "$conv_id" '.nodes[$id].subject') + + echo "Integration checkpoint reached: Task $conv_id ($SUBJECT)" >&2 + echo " All upstream tasks completed:" >&2 + for dep_id in $DEPS; do + DEP_OWNER=$(echo "$GRAPH" | jq -r --arg id "$dep_id" '.nodes[$id].owner') + echo " $dep_id ($DEP_OWNER)" >&2 + done + echo " These streams produced independent changes that must integrate at $conv_id." >&2 + echo " Recommend: verify interface compatibility before $conv_id starts." >&2 + + # List output files from upstream tasks + HAS_FILES=false + FILE_LIST="" + for dep_id in $DEPS; do + FILES=$(echo "$GRAPH" | jq -r --arg id "$dep_id" '.nodes[$id].output_files[]' 2>/dev/null) + if [ -n "$FILES" ]; then + HAS_FILES=true + FILE_LIST="${FILE_LIST}${FILES}"$'\n' + fi + done + if [ "$HAS_FILES" = true ]; then + echo " Shared interfaces: check these output files for contract alignment:" >&2 + echo "$FILE_LIST" | while IFS= read -r f; do + [ -n "$f" ] && echo " - $f" >&2 + done + fi +done + +exit 0 diff --git a/tests/hooks/test-check-integration-point.sh b/tests/hooks/test-check-integration-point.sh new file mode 100644 index 0000000..ba53afd --- /dev/null +++ b/tests/hooks/test-check-integration-point.sh @@ -0,0 +1,150 @@ +#!/bin/bash +# Tests for scripts/check-integration-point.sh (TaskCompleted hook — integration checkpoints) + +source "$(dirname "$0")/../lib/test-helpers.sh" + +HOOK="$PROJECT_ROOT/scripts/check-integration-point.sh" + +echo "Integration checkpoint hook tests" +echo "===================================" + +if ! command -v jq &>/dev/null; then + printf " ${YELLOW}SKIP${RESET} all — jq not installed\n" + exit 0 +fi + +# --- Test 1: No task-graph.json exits 0 silently --- +setup_temp_dir +cd "$TEST_TEMP_DIR" +setup_mock_workspace "test" +run_hook "$HOOK" '{"cwd":"'"$TEST_TEMP_DIR"'","team_name":"test"}' +assert_exit_code 0 "$HOOK_EXIT" "1: No task-graph.json exits 0 silently" +cleanup_temp_dir + +# --- Test 2: No convergence points fully unblocked — silent --- +setup_temp_dir +cd "$TEST_TEMP_DIR" +setup_mock_workspace "test" +# #1 completed, #2 still pending, #3 is convergence but not all deps done +setup_mock_task_graph "test" "$(cat <<'JSON' +{ + "team": "test", + "created": "2026-03-20T10:00:00Z", + "updated": "2026-03-20T10:30:00Z", + "nodes": { + "#1": {"subject":"Auth","owner":"impl-1","status":"completed","depends_on":[],"completed_at":"2026-03-20T10:30:00Z","output_files":["src/auth.ts"],"critical_path":true,"convergence_point":false}, + "#2": {"subject":"Session","owner":"impl-2","status":"pending","depends_on":[],"completed_at":null,"output_files":["src/session.ts"],"critical_path":false,"convergence_point":false}, + "#3": {"subject":"Middleware","owner":"impl-1","status":"pending","depends_on":["#1","#2"],"completed_at":null,"output_files":["src/mw.ts"],"critical_path":true,"convergence_point":true} + }, + "critical_path": ["#1","#3"], + "critical_path_length": 2 +} +JSON +)" +run_hook "$HOOK" '{"cwd":"'"$TEST_TEMP_DIR"'","team_name":"test"}' +assert_exit_code 0 "$HOOK_EXIT" "2: Partial convergence exits 0" +TESTS_TOTAL=$((TESTS_TOTAL + 1)) +if [ -z "$HOOK_STDERR" ]; then + TESTS_PASSED=$((TESTS_PASSED + 1)) + printf " ${GREEN}PASS${RESET} 2: Silent when convergence not fully unblocked\n" +else + TESTS_FAILED=$((TESTS_FAILED + 1)) + printf " ${RED}FAIL${RESET} 2: Expected silent, got: %s\n" "$HOOK_STDERR" +fi +cleanup_temp_dir + +# --- Test 3: All deps of convergence point completed — nudge --- +setup_temp_dir +cd "$TEST_TEMP_DIR" +setup_mock_workspace "test" +setup_mock_task_graph "test" "$(cat <<'JSON' +{ + "team": "test", + "created": "2026-03-20T10:00:00Z", + "updated": "2026-03-20T10:45:00Z", + "nodes": { + "#1": {"subject":"Auth","owner":"impl-1","status":"completed","depends_on":[],"completed_at":"2026-03-20T10:30:00Z","output_files":["src/auth.ts"],"critical_path":true,"convergence_point":false}, + "#2": {"subject":"Session","owner":"impl-2","status":"completed","depends_on":[],"completed_at":"2026-03-20T10:45:00Z","output_files":["src/session.ts"],"critical_path":false,"convergence_point":false}, + "#3": {"subject":"Middleware","owner":"impl-1","status":"pending","depends_on":["#1","#2"],"completed_at":null,"output_files":["src/mw.ts"],"critical_path":true,"convergence_point":true} + }, + "critical_path": ["#1","#3"], + "critical_path_length": 2 +} +JSON +)" +run_hook "$HOOK" '{"cwd":"'"$TEST_TEMP_DIR"'","team_name":"test"}' +assert_exit_code 0 "$HOOK_EXIT" "3: Convergence unblocked exits 0" +assert_stderr_contains "Integration checkpoint" "$HOOK_STDERR" "3: Shows integration checkpoint nudge" +assert_stderr_contains "Middleware" "$HOOK_STDERR" "3: Mentions the convergence task" +cleanup_temp_dir + +# --- Test 4: Convergence point already in_progress — skip --- +setup_temp_dir +cd "$TEST_TEMP_DIR" +setup_mock_workspace "test" +setup_mock_task_graph "test" "$(cat <<'JSON' +{ + "team": "test", + "created": "2026-03-20T10:00:00Z", + "updated": "2026-03-20T10:45:00Z", + "nodes": { + "#1": {"subject":"Auth","owner":"impl-1","status":"completed","depends_on":[],"completed_at":"2026-03-20T10:30:00Z","output_files":["src/auth.ts"],"critical_path":true,"convergence_point":false}, + "#2": {"subject":"Session","owner":"impl-2","status":"completed","depends_on":[],"completed_at":"2026-03-20T10:45:00Z","output_files":["src/session.ts"],"critical_path":false,"convergence_point":false}, + "#3": {"subject":"Middleware","owner":"impl-1","status":"in_progress","depends_on":["#1","#2"],"completed_at":null,"output_files":["src/mw.ts"],"critical_path":true,"convergence_point":true} + }, + "critical_path": ["#1","#3"], + "critical_path_length": 2 +} +JSON +)" +run_hook "$HOOK" '{"cwd":"'"$TEST_TEMP_DIR"'","team_name":"test"}' +assert_exit_code 0 "$HOOK_EXIT" "4: In-progress convergence exits 0" +TESTS_TOTAL=$((TESTS_TOTAL + 1)) +if [ -z "$HOOK_STDERR" ]; then + TESTS_PASSED=$((TESTS_PASSED + 1)) + printf " ${GREEN}PASS${RESET} 4: Silent when convergence already in_progress\n" +else + TESTS_FAILED=$((TESTS_FAILED + 1)) + printf " ${RED}FAIL${RESET} 4: Expected silent, got: %s\n" "$HOOK_STDERR" +fi +cleanup_temp_dir + +# --- Test 5: No convergence points in graph — silent --- +setup_temp_dir +cd "$TEST_TEMP_DIR" +setup_mock_workspace "test" +setup_mock_task_graph "test" "$(cat <<'JSON' +{ + "team": "test", + "created": "2026-03-20T10:00:00Z", + "updated": "2026-03-20T10:30:00Z", + "nodes": { + "#1": {"subject":"Auth","owner":"impl-1","status":"completed","depends_on":[],"completed_at":"2026-03-20T10:30:00Z","output_files":["src/auth.ts"],"critical_path":true,"convergence_point":false}, + "#2": {"subject":"Session","owner":"impl-2","status":"pending","depends_on":[],"completed_at":null,"output_files":["src/session.ts"],"critical_path":false,"convergence_point":false} + }, + "critical_path": ["#1"], + "critical_path_length": 1 +} +JSON +)" +run_hook "$HOOK" '{"cwd":"'"$TEST_TEMP_DIR"'","team_name":"test"}' +assert_exit_code 0 "$HOOK_EXIT" "5: No convergence points exits 0" +TESTS_TOTAL=$((TESTS_TOTAL + 1)) +if [ -z "$HOOK_STDERR" ]; then + TESTS_PASSED=$((TESTS_PASSED + 1)) + printf " ${GREEN}PASS${RESET} 5: Silent when no convergence points\n" +else + TESTS_FAILED=$((TESTS_FAILED + 1)) + printf " ${RED}FAIL${RESET} 5: Expected silent, got: %s\n" "$HOOK_STDERR" +fi +cleanup_temp_dir + +# --- Test 6: Empty input exits 0 --- +setup_temp_dir +cd "$TEST_TEMP_DIR" +run_hook "$HOOK" '{}' +assert_exit_code 0 "$HOOK_EXIT" "6: Empty input exits 0" +cleanup_temp_dir + +print_summary +exit "$TESTS_FAILED" From e74df684076faf73fca56c752f4aadecbdea5467 Mon Sep 17 00:00:00 2001 From: ducdmdev Date: Fri, 20 Mar 2026 09:57:00 +0700 Subject: [PATCH 07/22] feat: register 3 new DAG hook entries in hooks.json --- hooks/hooks.json | 27 +++++++++++++++++++++++++++ 1 file changed, 27 insertions(+) diff --git a/hooks/hooks.json b/hooks/hooks.json index 052f28a..be33c35 100644 --- a/hooks/hooks.json +++ b/hooks/hooks.json @@ -10,6 +10,24 @@ "timeout": 30 } ] + }, + { + "hooks": [ + { + "type": "command", + "command": "${CLAUDE_PLUGIN_ROOT}/scripts/compute-critical-path.sh", + "timeout": 15 + } + ] + }, + { + "hooks": [ + { + "type": "command", + "command": "${CLAUDE_PLUGIN_ROOT}/scripts/check-integration-point.sh", + "timeout": 15 + } + ] } ], "TeammateIdle": [ @@ -33,6 +51,15 @@ "timeout": 10 } ] + }, + { + "hooks": [ + { + "type": "command", + "command": "${CLAUDE_PLUGIN_ROOT}/scripts/detect-resume.sh", + "timeout": 15 + } + ] } ], "PreToolUse": [ From 1fdebc756657caaadc857e5334eb680ad7a9ff3d Mon Sep 17 00:00:00 2001 From: ducdmdev Date: Fri, 20 Mar 2026 09:57:49 +0700 Subject: [PATCH 08/22] docs: integrate critical path awareness into deadline escalation --- docs/coordination-advanced.md | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/docs/coordination-advanced.md b/docs/coordination-advanced.md index 4997e0b..aa69a1c 100644 --- a/docs/coordination-advanced.md +++ b/docs/coordination-advanced.md @@ -187,6 +187,11 @@ Proactive time-based escalation to prevent tasks from exceeding the user's time What's your progress? Use PROGRESS or COMPLETED format. If blocked, use BLOCKED so I can log and route it. ``` + + When checking stalled tasks, prioritize **critical-path tasks** (marked with `critical_path: true` in `task-graph.json`). A stalled critical-path task directly delays total completion. A stalled non-critical task has slack before it affects the timeline. Adjust escalation urgency accordingly: + - Critical-path task stalled → skip Nudge, go directly to **Warn** + - Non-critical task stalled → follow normal Nudge → Warn → Escalate ladder + 3. **Escalation ladder**: - **Nudge** (first check): request status update - **Warn** (second check, ~5 min later): "Task #N is at risk. Need status or BLOCKED report." From fc3c879797adebbd27f04d56e0dc7a1e02ba0792 Mon Sep 17 00:00:00 2001 From: ducdmdev Date: Fri, 20 Mar 2026 09:57:51 +0700 Subject: [PATCH 09/22] docs: add critical path metrics and CP column to report format --- docs/report-format.md | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/docs/report-format.md b/docs/report-format.md index 698bc8a..550b032 100644 --- a/docs/report-format.md +++ b/docs/report-format.md @@ -62,6 +62,9 @@ This file is generated during Phase 5, step 7. It is the last file written befor | Blocked events | {count} | | Remediation cycles | {0 or 1} | | Re-plans | {count, 0 if none} | +| Critical path length | {initial} → {final} (shifted {count} times) | +| Integration checkpoints | {count} ({passed}/{flagged}) | +| Resumed tasks | {count valid}/{count stale}/{count remaining} (or "N/A — fresh start") | --- @@ -75,9 +78,9 @@ This file is generated during Phase 5, step 7. It is the last file written befor ### Task Ledger -| ID | Subject | Owner | Status | Notes | -|----|---------|-------|--------|-------| -| {id} | {subject} | {owner} | completed / deferred | {outcome notes} | +| ID | Subject | Owner | Status | CP | Notes | +|----|---------|-------|--------|----|-------| +| {id} | {subject} | {owner} | completed / deferred | {★ if critical path} | {outcome notes} | ### Decision Log From 664d67c3657fb30f45bbde7d80eded7bb88584ad Mon Sep 17 00:00:00 2001 From: ducdmdev Date: Fri, 20 Mar 2026 09:58:37 +0700 Subject: [PATCH 10/22] docs: add task-graph.json schema and CP column to workspace-templates --- docs/workspace-templates.md | 96 +++++++++++++++++++++++++++++++++---- 1 file changed, 88 insertions(+), 8 deletions(-) diff --git a/docs/workspace-templates.md b/docs/workspace-templates.md index 9fd9e07..c9ff4c9 100644 --- a/docs/workspace-templates.md +++ b/docs/workspace-templates.md @@ -67,23 +67,23 @@ Cross-teammate information transfers. ## In Progress -| ID | Subject | Owner | Ref | Notes | -|----|---------|-------|-----|-------| +| ID | Subject | Owner | Ref | CP | Notes | +|----|---------|-------|-----|----|-------| ## Blocked -| ID | Subject | Owner | Ref | Blocked By | Notes | -|----|---------|-------|-----|-----------|-------| +| ID | Subject | Owner | Ref | CP | Blocked By | Notes | +|----|---------|-------|-----|----|-----------|-------| ## Pending -| ID | Subject | Owner | Ref | Blocked By | Notes | -|----|---------|-------|-----|-----------|-------| +| ID | Subject | Owner | Ref | CP | Blocked By | Notes | +|----|---------|-------|-----|----|-----------|-------| ## Completed -| ID | Subject | Owner | Ref | Notes | -|----|---------|-------|-----|-------| +| ID | Subject | Owner | Ref | CP | Notes | +|----|---------|-------|-----|----|-------| ```` ## issues.md @@ -122,9 +122,14 @@ The lead updates workspace files at every significant event. When multiple event |-------|------|---------------| | Team created | All 3 files | Initialize from templates | | Tasks created | tasks.md | Fill task ledger | +| Tasks created | task-graph.json | Initialize full graph with nodes, compute critical path and convergence points | | Teammate spawned | progress.md | Add row to Team Members | | Task started | tasks.md | Status -> `in_progress` | +| Task started | task-graph.json | Node status → `in_progress` | | Task completed | tasks.md | Status -> `completed`, add notes | +| Task completed | task-graph.json | Node status → `completed`, set `completed_at` and `output_files`, recompute `critical_path`. Self-check: read back to verify valid JSON. | +| Task blocked | task-graph.json | Node status → `blocked` | +| Re-plan occurs | task-graph.json | Rebuild graph from revised tasks | | Decision made | progress.md | Append to Decision Log | | Handoff occurs | progress.md | Append to Handoffs | | Issue found | issues.md | Append row, update Open count | @@ -151,6 +156,81 @@ Created during Phase 3 after spawning teammates. Maps each teammate to their own } ``` +### task-graph.json + +Created during Phase 3 step 4a immediately after creating all tasks. Contains the full dependency graph as a DAG with critical path and convergence point metadata. Read by `compute-critical-path.sh`, `detect-resume.sh`, and `check-integration-point.sh` hooks. + +**When to create**: ALL archetypes. + +> After updating, read the file back to verify valid JSON — malformed JSON silently disables all three hook scripts. + +#### Schema + +```json +{ + "team": "{team-name}", + "created": "{ISO 8601 timestamp}", + "updated": "{ISO 8601 timestamp}", + "nodes": { + "#{id}": { + "subject": "{task subject line}", + "owner": "{teammate-name}", + "status": "pending|in_progress|completed|blocked", + "depends_on": ["#{id}"], + "completed_at": "{ISO 8601 timestamp}|null", + "output_files": ["{relative file paths}"], + "critical_path": true, + "convergence_point": true + } + }, + "critical_path": ["#{id}"], + "critical_path_length": 0 +} +``` + +#### Field Reference + +| Field | Type | Description | +|---|---|---| +| `team` | string | Team name matching TeamCreate | +| `created` | ISO timestamp | When the graph was first created | +| `updated` | ISO timestamp | Last modification time | +| `nodes` | object | Map of task ID → node data | +| `nodes.*.subject` | string | Task subject line | +| `nodes.*.owner` | string | Assigned teammate name | +| `nodes.*.status` | enum | `pending`, `in_progress`, `completed`, `blocked` | +| `nodes.*.depends_on` | string[] | Task IDs this node depends on | +| `nodes.*.completed_at` | timestamp/null | When the task was completed (null if not yet) | +| `nodes.*.output_files` | string[] | Relative file paths produced by this task | +| `nodes.*.critical_path` | boolean | Whether this node is on the current critical path | +| `nodes.*.convergence_point` | boolean | Whether this node has 2+ upstream dependencies. Scripts derive converging task IDs from `depends_on` when this is `true` — no separate `converges_from` field needed. | +| `critical_path` | string[] | Ordered list of task IDs forming the current critical path | +| `critical_path_length` | number | Number of nodes on the critical path | + +#### Lifecycle + +| Phase | Action | +|---|---| +| Phase 3 step 4a | Create with full graph. Compute initial critical path and convergence points | +| Phase 4 (STARTING) | Update node status to `in_progress` | +| Phase 4 (COMPLETED) | Update node status to `completed`, set `completed_at` and `output_files`. Recompute `critical_path`. **Self-check**: read the file back after editing to verify JSON is valid — malformed JSON silently disables all three hook scripts. | +| Phase 4 (BLOCKED) | Update node status to `blocked` | +| Phase 4 (re-plan) | Rebuild graph from revised task set | +| Phase 5 | Final state preserved as audit artifact | +| Resume | Read by `detect-resume.sh`, stale nodes reset to `pending` | + +#### Applicability by Archetype + +| Archetype | Create task-graph.json? | Critical path useful? | Convergence points useful? | Resume useful? | +|---|---|---|---|---| +| Implementation | Yes | Yes — prioritize build chain | Yes — diamond deps on shared interfaces | Yes — code artifacts have output_files | +| Research | Yes | Yes — prioritize blocking research angles | Rare — research streams usually independent | Limited — no output files to validate | +| Audit | Yes | Yes — prioritize blocking audit lenses | Rare — audit lenses usually independent | Limited — no output files to validate | +| Planning | Yes | Yes — prioritize blocking planning concerns | Sometimes — design decisions may converge | Limited — workspace-only outputs | +| Hybrid | Yes | Yes | Yes — mixed streams often converge | Yes — implementation components have output_files | + +Note: For read-only archetypes (Research, Audit, Planning), `output_files` will typically be empty or reference workspace files. Staleness validation in resume mode uses git-tracked files only, so resume is most valuable for Implementation and Hybrid teams. + ### events.log Created by the SubagentStart/SubagentStop hooks during Phase 4. Each line is a JSON object recording teammate spawn and stop events. Used for post-mortem analysis. From f9ecc45b2a9b89f86892faa04ba6a45b59bb973a Mon Sep 17 00:00:00 2001 From: ducdmdev Date: Fri, 20 Mar 2026 09:58:37 +0700 Subject: [PATCH 11/22] docs: add resume and integration checkpoint patterns to coordination-patterns --- docs/coordination-patterns.md | 35 +++++++++++++++++++++++++++++++++++ 1 file changed, 35 insertions(+) diff --git a/docs/coordination-patterns.md b/docs/coordination-patterns.md index bd93a9d..4dfea77 100644 --- a/docs/coordination-patterns.md +++ b/docs/coordination-patterns.md @@ -19,7 +19,9 @@ Patterns for the lead to handle common coordination scenarios during Phase 4. - [Synthesis Pattern](#synthesis-pattern) — collecting final results - [Error Recovery](#error-recovery) — handling teammate errors - [Issue Triage After Context Recovery](#issue-triage-after-context-recovery) — post-compaction review +- [Resume from Existing Workspace](#resume-from-existing-workspace) — recovering from a previous team session - [Direct Handoff](#direct-handoff) — authorized peer-to-peer messaging with audit trail +- [Integration Checkpoint Response](#integration-checkpoint-response) — handling convergence point nudges - [Advanced Patterns](coordination-advanced.md) — re-plan, adversarial review, checkpoint/rollback, deadline escalation, and more ## Communication Protocol @@ -161,6 +163,29 @@ Recovery actions for common Phase 3 failures. | Teammate fails to spawn | Check the error. Common causes: tool not available, permission denied. Retry once. If still failing, log to `issues.md`, continue with remaining teammates, reassign orphaned tasks | | Context compaction during Phase 3 | On recovery, read workspace files. If they exist but tasks/teammates are incomplete, resume from where you left off. If workspace doesn't exist yet, restart Phase 3 | +## Resume from Existing Workspace + +When starting a new team session and the `detect-resume.sh` hook reports a resumable workspace: + +### Valid Completed Tasks +Tasks whose output files are unchanged since `completed_at`. Skip these entirely — do not re-create or re-assign. Their results carry forward. + +### Stale Completed Tasks +Tasks whose output files were modified after `completed_at` (someone edited the files outside the team). These must be re-run: +- Reset status to `pending` in `task-graph.json` +- Create new TaskCreate entries for them +- Assign to appropriate teammates +- Log in `progress.md` Decision Log: "Resumed — task #N marked stale (output modified after completion)" + +### Remaining Tasks +Tasks that were never completed. Create and assign normally. + +### Archive Protocol +If the user chooses "start fresh" instead of resuming: +- Rename `.agent-team/{team-name}/` to `.agent-team/{team-name}-archived/` +- Proceed with normal Phase 3 +- The archived workspace is preserved for reference + ## File Conflict Resolution When two teammates report working on the same file: @@ -311,6 +336,16 @@ For pre-approved information transfers between specific teammates, bypassing the The audit trail MUST be maintained. Direct handoffs save time but must still be logged via the lead's workspace updates. +## Integration Checkpoint Response + +When `check-integration-point.sh` fires an integration nudge after two converging streams complete: + +1. **Read the nudge** — identify which convergence point was unblocked and which upstream tasks produced the converging outputs +2. **Quick compatibility check** — read the `output_files` from both upstream tasks in `task-graph.json`. Do they define compatible interfaces? (e.g., if task #1 exports `TokenResult` and task #2 imports `TokenResult`, do the types match?) +3. **If compatible** — message the convergence task owner: "Upstream tasks #X and #Y both completed. Interfaces verified compatible. Proceed with task #Z." +4. **If unclear or incompatible** — message both upstream owners and the convergence owner: "Integration issue at #Z: outputs from #X and #Y may conflict at [interface]. Verify before proceeding." Log in `issues.md` as **medium** severity. +5. **Log** — record the checkpoint in `progress.md` Decision Log: "Integration checkpoint: #Z unblocked by #X + #Y, compatibility [verified|flagged]" + ## See Also - [coordination-advanced.md](coordination-advanced.md) — Re-plan on Block, Adversarial Review, Quality Gate, Checkpoint/Rollback, Deadline Escalation, Circular Dependency Detection, Graceful Degradation, Auto-Block, Anti-Patterns From 87af72b0a68eb16dec457310fd07b5ff1f928119 Mon Sep 17 00:00:00 2001 From: ducdmdev Date: Fri, 20 Mar 2026 09:59:32 +0700 Subject: [PATCH 12/22] docs: add task-graph.json step 4a reference to all archetype skills --- skills/agent-audit/SKILL.md | 2 ++ skills/agent-implement/SKILL.md | 4 +++- skills/agent-plan/SKILL.md | 2 ++ skills/agent-research/SKILL.md | 2 ++ skills/agent-team/SKILL.md | 2 ++ 5 files changed, 11 insertions(+), 1 deletion(-) diff --git a/skills/agent-audit/SKILL.md b/skills/agent-audit/SKILL.md index e2e1f19..1e9072f 100644 --- a/skills/agent-audit/SKILL.md +++ b/skills/agent-audit/SKILL.md @@ -29,6 +29,8 @@ Apply shared Phase 1a (plan detection & preparation) and Phase 1b (decompose fro ## Phase 3 Override: Workspace Setup Apply shared Phase 3 steps 1-7, with these differences: + +After shared Phase 3 step 4 (create tasks), execute step 4a: create `task-graph.json` with initial critical path and convergence points. See [workspace-templates.md](../../docs/workspace-templates.md#task-graphjson) for schema. - **SKIP file-locks.json** — all teammates are read-only - **SKIP branch instructions** — no code branches needed - File ownership hook (PreToolUse) is N/A for this archetype diff --git a/skills/agent-implement/SKILL.md b/skills/agent-implement/SKILL.md index e1b7a84..eb2d596 100644 --- a/skills/agent-implement/SKILL.md +++ b/skills/agent-implement/SKILL.md @@ -24,6 +24,8 @@ Apply shared Phase 1a (plan detection & preparation) and Phase 1b (decompose fro Apply shared Phase 3 steps 1-7, plus: +After shared Phase 3 step 4 (create tasks), execute step 4a: create `task-graph.json` with initial critical path and convergence points. See [workspace-templates.md](../../docs/workspace-templates.md#task-graphjson) for schema. + ### file-locks.json Create `.agent-team/{team-name}/file-locks.json` mapping each teammate to owned files/directories. Used by PreToolUse hook. See [workspace-templates.md](../../docs/workspace-templates.md#file-locksjson) for format. @@ -83,7 +85,7 @@ Run checks in order. Items marked ★ are project-specific — PASS automaticall | 1 | **Uncommitted changes** | `git status` scoped to each implementer's owned files | All owned files committed | Message implementer to commit | | 2 | **Build & tests** | Assign teammate: "Run build + test commands, report PASS/FAIL" | Exit 0, all tests pass | Create fix task | | 3 | **Lint/format** ★ | Assign teammate: "Run lint, report new warnings/errors" | No new lint errors | Create fix task | -| 4 | **Integration** | Assign teammate: "Verify cross-module connections" | Cross-teammate outputs connect | Create integration fix task | +| 4 | **Integration** | Assign teammate: "Verify cross-module connections". If any convergence points in `task-graph.json` were flagged during Phase 4, verify they were resolved. | Cross-teammate outputs connect, flagged convergence points resolved | Create integration fix task | | 5 | **Security scan** ★ | Assign teammate: "Check for secrets, OWASP top 10 in changed files" | No new security issues | Create fix task (critical) | | 6 | **Workspace issues** | Read `issues.md` | 0 OPEN issues | Route to teammate | | 7 | **Plan completion** | Compare Phase 2 plan vs TaskList | Every stream has completed tasks | Create missing tasks | diff --git a/skills/agent-plan/SKILL.md b/skills/agent-plan/SKILL.md index f58a034..7842d54 100644 --- a/skills/agent-plan/SKILL.md +++ b/skills/agent-plan/SKILL.md @@ -29,6 +29,8 @@ Apply shared Phase 1a (plan detection & preparation) and Phase 1b (decompose fro ## Phase 3 Override: Workspace Setup Apply shared Phase 3 steps 1-7, with these differences: + +After shared Phase 3 step 4 (create tasks), execute step 4a: create `task-graph.json` with initial critical path and convergence points. See [workspace-templates.md](../../docs/workspace-templates.md#task-graphjson) for schema. - **SKIP file-locks.json** — Planners write docs to workspace, not project files - **SKIP branch instructions** — no code branches - If multiple Planners, assign distinct workspace sub-paths (e.g., `{workspace}/planner-1/`) to avoid write conflicts diff --git a/skills/agent-research/SKILL.md b/skills/agent-research/SKILL.md index d70c94e..0b55758 100644 --- a/skills/agent-research/SKILL.md +++ b/skills/agent-research/SKILL.md @@ -29,6 +29,8 @@ Apply shared Phase 1a (plan detection & preparation) and Phase 1b (decompose fro ## Phase 3 Override: Workspace Setup Apply shared Phase 3 steps 1-7, with these differences: + +After shared Phase 3 step 4 (create tasks), execute step 4a: create `task-graph.json` with initial critical path and convergence points. See [workspace-templates.md](../../docs/workspace-templates.md#task-graphjson) for schema. - **SKIP file-locks.json** — all teammates are read-only, no file ownership to enforce - **SKIP branch instructions** — no code branches needed - File ownership hook (PreToolUse) is N/A for this archetype diff --git a/skills/agent-team/SKILL.md b/skills/agent-team/SKILL.md index 0cd15b1..86f57fa 100644 --- a/skills/agent-team/SKILL.md +++ b/skills/agent-team/SKILL.md @@ -44,6 +44,8 @@ Then in Phase 1b, apply the shared decomposition steps plus: Apply shared Phase 3 steps 1-7, plus: +After shared Phase 3 step 4 (create tasks), execute step 4a: create `task-graph.json` with initial critical path and convergence points. See [workspace-templates.md](../../docs/workspace-templates.md#task-graphjson) for schema. + ### file-locks.json (conditional) Create **only if ANY teammate writes project files** (implementers, writers with file ownership). Skip if all teammates are read-only. From 77d52b366068f52d89d3526f6cea2805c7034012 Mon Sep 17 00:00:00 2001 From: ducdmdev Date: Fri, 20 Mar 2026 10:01:46 +0700 Subject: [PATCH 13/22] docs: add critical path, resume detection, and convergence points to shared phases --- docs/shared-phases.md | 46 ++++++++++++++++++++++++++++++++++++++++--- 1 file changed, 43 insertions(+), 3 deletions(-) diff --git a/docs/shared-phases.md b/docs/shared-phases.md index 405709f..20c34fe 100644 --- a/docs/shared-phases.md +++ b/docs/shared-phases.md @@ -14,7 +14,10 @@ Shared phase logic for all agent-team archetype skills. Each archetype skill ref - [Phase 1b: Decompose from Plan](#phase-1b-decompose-from-plan) - [Phase 2: Present Plan to User](#phase-2-present-plan-to-user-mandatory--do-not-skip) - [Phase 3: Create Team](#phase-3-create-team-shared-steps) + - [Resume Detection (Step 1a)](#phase-3-create-team-shared-steps) + - [Task Graph Creation (Step 4a)](#phase-3-create-team-shared-steps) - [Phase 4: Coordinate](#phase-4-coordinate) + - [Critical Path Awareness](#critical-path-awareness) - [Phase 5: Synthesis and Completion](#phase-5-synthesis-and-completion-shared-steps) - [Anti-Patterns](#anti-patterns) - [Reference](#reference) @@ -202,8 +205,9 @@ User has approved a plan (or no plan — see fallback below). The decomposition 3. **Derive dependencies from plan** — The plan's task ordering and blocked-by relationships translate directly to Agent Team task dependencies. 4. **Determine if a team is warranted** — if fewer than 2 independent streams exist, tell the user a single session is more efficient. Offer: "This plan is sequential — shall I execute it directly without a team?" Stop here if not warranted. 5. **Integration points** — for each pair of streams, identify where plan tasks reference shared interfaces, contracts, or outputs. These become explicit handoff points in Phase 2. -6. **Identify reference documents** — already gathered during Phase 1a. Carry forward into workspace. If Phase 1a was skipped (trivial task early exit), find specs, ADRs, design docs, PRs, or other docs relevant to the task. -7. **Check for custom roles** — if `docs/custom-roles.md` exists in the project, read it. Use custom roles alongside built-in roles when they match the task requirements. +6. **Mark convergence points** — for each task that depends on 2+ upstream tasks, flag it as a convergence point. These become integration checkpoints during Phase 4 — the `check-integration-point.sh` hook will nudge the lead to verify interface compatibility when all upstream tasks complete. Include convergence points in the Phase 2 presentation. +7. **Identify reference documents** — already gathered during Phase 1a. Carry forward into workspace. If Phase 1a was skipped (trivial task early exit), find specs, ADRs, design docs, PRs, or other docs relevant to the task. +8. **Check for custom roles** — if `docs/custom-roles.md` exists in the project, read it. Use custom roles alongside built-in roles when they match the task requirements. **Fallback — no plan available:** If Phase 1a was skipped (trivial task early exit) or the user declined all plans, the Team Lead performs ad-hoc decomposition using the strategies below: - **By module/area**: frontend vs backend, auth vs payments (best for feature work) @@ -236,6 +240,10 @@ Task breakdown: 2. [task] -> assigned to [role] 3. [task] -> assigned to [role] (blocked by #1) +Critical path: [#X → #Y → #Z] (length: N) + Non-critical (can slip without affecting total time): [#A, #B] + Integration checkpoints: [#Y (converges #X + #A — verify interface compatibility)] + Every phase has an owner (omit for pure review tasks): - Setup/config: [role] - Implementation: [role(s)] @@ -254,6 +262,7 @@ Estimated teammates: N 1. "Is this plan complex? Complexity signals: multi-module/area changes, architectural decisions, risky refactors, multiple implementers with cross-dependencies, security-sensitive changes, new integrations. If yes, does the teammate list include a **dedicated reviewer** AND a **dedicated tester** (separate teammates, not combined)? If no, add them before presenting." 2. "Have I presented this plan AND received user confirmation?" If no, STOP. 3. "Do any tasks form circular dependencies? Trace each `blocked by` chain — if task A blocks B blocks C blocks A, that's a cycle. If found, restructure: merge the cyclic tasks or break the cycle by removing one dependency." +4. "Have I identified the critical path? Is it displayed in the plan? Are convergence points marked?" Wait for user confirmation before proceeding. @@ -261,6 +270,23 @@ Wait for user confirmation before proceeding. Steps shared by all archetypes. Archetype-specific overrides (file-locks, branches, roles) are in each skill's own Phase 3 section. +1a. **Check for resumable workspace** — if the `detect-resume.sh` hook surfaced a resumable workspace at session start, present the resume option to the user: + +``` +Existing workspace found: .agent-team/{team-name}/ + Completed (valid): {list with task IDs and subjects} + Completed (stale): {list — output files modified since completion} + Remaining: {list} + +Options: +1. Resume — skip valid completed tasks, re-run stale tasks, continue with remaining +2. Start fresh — archive existing workspace to .agent-team/{team-name}-archived/, create new +``` + +If resuming: skip TeamCreate (team may still exist), reuse workspace, create only remaining + stale tasks. Update `task-graph.json` — reset stale nodes to `pending`, preserve valid completed nodes. Log in `progress.md` Decision Log. Proceed to step 5 (spawn teammates). + +If starting fresh: rename existing workspace directory with `-archived` suffix, proceed normally from step 2. + 1. **Check for existing team** — read `~/.claude/teams/` to see if a team already exists. If one does, ask the user whether to clean it up first or work within it. 2. **Create team**: @@ -295,6 +321,8 @@ Steps shared by all archetypes. Archetype-specific overrides (file-locks, branch - **Update workspace**: record all tasks in `tasks.md` - **Self-check**: "Does every task have a verifiable completion criterion — something a teammate can confirm as done or not done?" If any task says just "implement X" without a success condition, rewrite it. +4a. **Create `task-graph.json`** — immediately after creating all tasks, generate `.agent-team/{team-name}/task-graph.json` with the full dependency graph. Compute the initial critical path (longest chain, tie-break by lowest task ID) and mark convergence points (nodes with 2+ dependencies). Validate the graph is acyclic — if a cycle is detected, fix it before proceeding (see Circular Dependency Detection in [coordination-advanced.md](coordination-advanced.md)). Update `tasks.md` with ★ markers on critical-path tasks and convergence notes. See [workspace-templates.md](workspace-templates.md#task-graphjson) for schema. + 5. **Spawn teammates** using the Task tool with `team_name`, `name`, and `subagent_type` parameters. See [teammate-roles.md](teammate-roles.md) for role overview and [spawn-templates.md](spawn-templates.md) for detailed spawn prompt templates. **subagent_type**: `"general-purpose"` for full tool access (implementers, challengers, testers). `"Explore"` for read-only research teammates. `"general-purpose"` if a reviewer needs Bash. Optionally set `mode: "plan"` for risky or architectural tasks. @@ -373,12 +401,13 @@ When receiving structured messages: | Prefix | Lead Action | |--------|--------------| | STARTING | Update `tasks.md` status to `in_progress`, add note | -| COMPLETED | Update `tasks.md` status to `completed`, add file list and notes. Check: does this unblock other tasks? If yes, message the dependent teammate | +| COMPLETED | Update `tasks.md` status to `completed`, add file list and notes. Update `task-graph.json`: set node status to `completed`, record `completed_at` and `output_files`. **Self-check**: read `task-graph.json` back to verify valid JSON — malformed JSON silently disables all three hook scripts. Check: does this unblock other tasks? If yes, message the dependent teammate. The `compute-critical-path.sh` hook will output the updated critical path. | | BLOCKED | Add row to `issues.md` immediately. Acknowledge the teammate. Route to resolution | | HANDOFF | Extract key details, forward to dependent teammate with actionable context. Log in `progress.md` Handoffs | | QUESTION | Check if answer is in workspace files. If yes, answer with file reference. If no, investigate | | PROGRESS | Note milestone in `tasks.md` Notes column. If percent indicates near-completion, no action needed. If stalled, trigger Deadline Escalation | | CHECKPOINT | If `ready_for` lists task IDs, forward checkpoint details to dependent teammate. Log in `progress.md` Handoffs | +| (hook: integration checkpoint) | Read the nudge from `check-integration-point.sh`. Before unblocking the convergence task, verify interface compatibility between upstream outputs. If compatible, message the convergence task owner to proceed. If unclear, log in `issues.md` as medium severity. Log checkpoint in `progress.md` Decision Log. | #### Plan Approval Handling @@ -386,6 +415,17 @@ When a teammate spawned with `mode: "plan"` finishes planning, they send a `plan For high-frequency handoffs between specific teammates, you may authorize direct communication — see the Direct Handoff pattern in [coordination-patterns.md](coordination-patterns.md). The audit trail must still be maintained in `progress.md`. +### Critical Path Awareness + +The critical path determines total execution time. The `compute-critical-path.sh` hook outputs the remaining critical path after every task completion. Use it to prioritize: + +- **BLOCKED on critical path** → resolve immediately (highest-priority coordination action) +- **BLOCKED on non-critical path** → resolve normally (slippage has slack) +- **Teammate idle on critical path** → reassign work to keep the critical chain moving +- **Teammate idle on non-critical path** → lower priority, consider assigning critical-path support tasks + +After every task completion, read the hook output. If the critical path shifted (a previously non-critical chain is now longest), update `task-graph.json` and the ★ markers in `tasks.md`. + ### Coordination Patterns For detailed patterns on these scenarios, see [coordination-patterns.md](coordination-patterns.md): From 86b4511295373cdea7d7342088e5ef71aa88886c Mon Sep 17 00:00:00 2001 From: ducdmdev Date: Fri, 20 Mar 2026 10:03:00 +0700 Subject: [PATCH 14/22] test: add task-graph.json and DAG script reference assertions --- tests/structure/test-doc-references.sh | 17 +++++++++++++++++ 1 file changed, 17 insertions(+) diff --git a/tests/structure/test-doc-references.sh b/tests/structure/test-doc-references.sh index d5207cc..0ef337a 100755 --- a/tests/structure/test-doc-references.sh +++ b/tests/structure/test-doc-references.sh @@ -112,5 +112,22 @@ fi assert_true "Counter separator '--' consistent across $SEPARATOR_MATCHES/$SEPARATOR_EXPECTED sources" "[ $SEPARATOR_MATCHES -ge 2 ]" +# --- Test: workspace-templates.md references task-graph.json --- +TASK_GRAPH_REF=$(grep -c 'task-graph.json' docs/workspace-templates.md) +assert_true "workspace-templates.md references task-graph.json" "[ $TASK_GRAPH_REF -gt 0 ]" + +# --- Test: All SKILL.md files reference step 4a or task-graph.json --- +for SKILL_MD in skills/*/SKILL.md; do + SKILL_NAME=$(basename "$(dirname "$SKILL_MD")") + STEP4A_REF=$(grep -c 'step 4a\|task-graph.json' "$SKILL_MD") + assert_true "$SKILL_NAME: SKILL.md references step 4a or task-graph.json" "[ $STEP4A_REF -gt 0 ]" +done + +# --- Test: New DAG scripts referenced in docs --- +for script_name in compute-critical-path.sh detect-resume.sh check-integration-point.sh; do + SCRIPT_REF=$(grep -rl "$script_name" docs/ | wc -l | tr -d ' ') + assert_true "$script_name referenced in docs/" "[ $SCRIPT_REF -gt 0 ]" +done + print_summary exit "$TESTS_FAILED" From dd83fd579e78a3bdcdb7481f3d3a40cae37964a3 Mon Sep 17 00:00:00 2001 From: ducdmdev Date: Fri, 20 Mar 2026 10:03:02 +0700 Subject: [PATCH 15/22] docs: add DAG hooks and task-graph.json to README --- README.md | 29 +++++++++++++++++++++++++++-- 1 file changed, 27 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index 6ee85df..e3d6b78 100644 --- a/README.md +++ b/README.md @@ -261,6 +261,27 @@ Tracks teammate lifecycle in `events.log`: - Logs spawn and stop events with timestamps and teammate metadata - Provides post-mortem analysis data +### ComputeCriticalPath (TaskCompleted) + +Recomputes and displays the critical path after each task completion: +- Reads `task-graph.json` for the dependency graph +- Outputs remaining critical path and identifies blocked critical tasks +- Informational only — always allows task completion + +### DetectResume (SessionStart) + +Detects resumable workspaces at session start: +- Scans for incomplete `task-graph.json` files in `.agent-team/` +- Validates completed task output files via git timestamps (valid/stale/missing) +- Outputs resume context with options to resume or start fresh + +### CheckIntegrationPoint (TaskCompleted) + +Detects when convergence points become fully unblocked: +- Checks if all upstream tasks of a convergence point are completed +- Nudges the lead to verify interface compatibility before downstream task starts +- Informational only — silent when no convergence point is ready + All hooks degrade gracefully — exit 0 if `jq` is missing. ## Workspace @@ -270,9 +291,10 @@ Each team creates a persistent workspace at `.agent-team/{team-name}/` in your p ``` .agent-team/0304-refactor-auth/ ├── progress.md # Team status, members, decisions, handoffs -├── tasks.md # Task ledger with status and dependencies +├── tasks.md # Task ledger with status tracking ├── issues.md # Issue tracker with severity and resolution ├── file-locks.json # File ownership map (teammate -> files/directories) +├── task-graph.json # DAG: task dependencies, critical path, convergence points ├── events.log # Structured JSON event log for post-mortem analysis └── report.md # Final report (generated at completion) ``` @@ -297,7 +319,10 @@ agent-team-plugin/ │ ├── check-file-ownership.sh # PreToolUse(Write|Edit) hook │ ├── track-teammate-lifecycle.sh # SubagentStart/Stop hook │ ├── setup-worktree.sh # Worktree creation for isolation mode -│ └── merge-worktrees.sh # Worktree merge in Phase 5 +│ ├── merge-worktrees.sh # Worktree merge in Phase 5 +│ ├── compute-critical-path.sh # ComputeCriticalPath hook +│ ├── detect-resume.sh # DetectResume hook +│ └── check-integration-point.sh # CheckIntegrationPoint hook ├── skills/ │ ├── agent-team/ │ │ └── SKILL.md # Hybrid/catch-all orchestrator From 4564f3f880d718111b4b151dafc3c833bfeb4b83 Mon Sep 17 00:00:00 2001 From: ducdmdev Date: Fri, 20 Mar 2026 10:03:15 +0700 Subject: [PATCH 16/22] docs: update CLAUDE.md file ownership and hook counts for v2.6.0 --- CLAUDE.md | 11 +++++++---- 1 file changed, 7 insertions(+), 4 deletions(-) diff --git a/CLAUDE.md b/CLAUDE.md index 9d795ef..f597f68 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -32,8 +32,8 @@ docs/ Shared phases + reference docs consumed by skills at runt |------|---------|----------------| | `.claude-plugin/plugin.json` | Plugin identity | Bump version here on release | | `.claude-plugin/marketplace.json` | Marketplace registry | Bump version here too, keep in sync with plugin.json | -| `hooks/hooks.json` | Hook registration (6 hooks) | Update timeout values, add new hooks, or update hook command paths | -| `scripts/*.sh` | Hook enforcement logic (7 scripts) | Written in bash (`#!/bin/bash`), degrade gracefully without `jq` | +| `hooks/hooks.json` | Hook registration (9 hook entries) | Update timeout values, add new hooks, or update hook command paths | +| `scripts/*.sh` | Hook enforcement logic (12 scripts) | Written in bash (`#!/bin/bash`), degrade gracefully without `jq` | | `skills/agent-team/SKILL.md` | Hybrid/catch-all skill | Archetype detection + hybrid-specific overrides | | `skills/agent-implement/SKILL.md` | Implementation skill | Implementation-specific Phase 3/5 | | `skills/agent-research/SKILL.md` | Research skill | Research-specific Phase 3/5 | @@ -45,7 +45,7 @@ docs/ Shared phases + reference docs consumed by skills at runt | `docs/communication-protocol.md` | Structured message formats | Update when changing protocol prefixes or role-specific formats | | `docs/coordination-patterns.md` | Core conflict resolution, handoffs | Update when adding new core coordination patterns | | `docs/coordination-advanced.md` | Advanced coordination patterns | Update when adding new advanced patterns | -| `docs/workspace-templates.md` | Workspace file templates | Update when adding new workspace files | +| `docs/workspace-templates.md` | Workspace file templates + `task-graph.json` schema | Update when adding new workspace files or changing DAG schema | | `docs/report-format.md` | Final report template | Update when changing report structure | | `docs/custom-roles.md` | Project-specific role template | Reference for users creating custom roles | | `docs/team-archetypes.md` | Team type definitions + phase profiles | Update when adding new archetypes or modifying phase overrides | @@ -114,7 +114,7 @@ Then trigger with: "use agent team to [task]" ### Verify Hooks -Six hooks registered in `hooks/hooks.json`: +Nine hook entries registered in `hooks/hooks.json`: 1. **TaskCompleted** — try marking a task complete without file changes (should block) 2. **TeammateIdle** — let a teammate go idle with in-progress tasks (should nudge) @@ -122,6 +122,9 @@ Six hooks registered in `hooks/hooks.json`: 4. **PreToolUse(Write|Edit)** — have a teammate edit another's file (should warn, then block) 5. **SubagentStart** — spawn a teammate (should log to events.log) 6. **SubagentStop** — teammate shuts down (should log to events.log) +7. **ComputeCriticalPath** — complete a task and check stderr for critical path update +8. **DetectResume** — start a new session with an incomplete workspace and check stdout for resume context +9. **CheckIntegrationPoint** — complete both upstream tasks of a convergence point and check stderr for integration nudge ## Common Tasks From 660e015273fe11d1b261bba265f3bc3191bd9b3a Mon Sep 17 00:00:00 2001 From: ducdmdev Date: Fri, 20 Mar 2026 10:05:19 +0700 Subject: [PATCH 17/22] chore: bump version to 2.6.0 --- .claude-plugin/marketplace.json | 3 +-- .claude-plugin/plugin.json | 2 +- CHANGELOG.md | 22 +++++++++++++++++++ ...6-03-20-dag-aligned-improvements-design.md | 2 +- 4 files changed, 25 insertions(+), 4 deletions(-) diff --git a/.claude-plugin/marketplace.json b/.claude-plugin/marketplace.json index 039a171..8b5d682 100644 --- a/.claude-plugin/marketplace.json +++ b/.claude-plugin/marketplace.json @@ -1,6 +1,5 @@ { "name": "agent-team-plugin", - "description": "Claude Code plugin for orchestrating parallel work via Agent Teams", "owner": { "name": "ducdmdev" }, @@ -8,7 +7,7 @@ { "name": "agent-team", "description": "Orchestrates parallel work via Agent Teams with automated coordination, workspace tracking, and hook enforcement", - "version": "2.5.1", + "version": "2.6.0", "source": { "source": "url", "url": "https://github.com/ducdmdev/agent-team-plugin.git" diff --git a/.claude-plugin/plugin.json b/.claude-plugin/plugin.json index caef918..08cf1d2 100644 --- a/.claude-plugin/plugin.json +++ b/.claude-plugin/plugin.json @@ -1,7 +1,7 @@ { "name": "agent-team", "description": "Orchestrates parallel work via Agent Teams with automated coordination, workspace tracking, and hook enforcement", - "version": "2.5.1", + "version": "2.6.0", "author": { "name": "Duc Do" } diff --git a/CHANGELOG.md b/CHANGELOG.md index abbaed8..f2f9b87 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,6 +5,28 @@ All notable changes to this project will be documented in this file. The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/), and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). +## [2.6.0] - 2026-03-20 + +### Added +- **`task-graph.json` workspace file** — centralized DAG with task dependencies, critical path, and convergence points. Created in Phase 3, maintained by lead, read by 3 new hook scripts +- **`compute-critical-path.sh` hook** (TaskCompleted) — recomputes and displays remaining critical path after each task completion, warns about blocked critical tasks +- **`detect-resume.sh` hook** (SessionStart) — detects resumable workspaces with smart staleness validation via git timestamps (valid/stale/missing output files) +- **`check-integration-point.sh` hook** (TaskCompleted) — detects when convergence points (diamond dependencies) become fully unblocked, nudges lead to verify interface compatibility +- **Critical Path Awareness** in Phase 4 — lead prioritizes critical-path blockers over non-critical work +- **Resume from Existing Workspace** coordination pattern — valid/stale/remaining protocol with archive option +- **Integration Checkpoint Response** coordination pattern — lead response protocol for convergence nudges +- **CP column** in `tasks.md` — ★ marks critical path tasks, convergence notes in the Notes column + +### Changed +- Phase 1b gains convergence point marking (step 6 after integration points) +- Phase 2 gains critical path display and integration checkpoint preview +- Phase 3 gains resume detection (step 1a) and `task-graph.json` creation (step 4a) +- Phase 4 gains critical-path-weighted prioritization and integration checkpoint processing +- Deadline Escalation gains critical-path acceleration (skip Nudge, go to Warn) +- Report gains critical path metrics (initial → final length, shift count) and integration checkpoint counts +- All 5 archetype SKILL.md files reference step 4a +- `agent-implement` completion gate check #4 gains convergence-point awareness + ## [2.5.1] - 2026-03-17 ### Changed diff --git a/docs/specs/2026-03-20-dag-aligned-improvements-design.md b/docs/specs/2026-03-20-dag-aligned-improvements-design.md index 2688aa2..85f7190 100644 --- a/docs/specs/2026-03-20-dag-aligned-improvements-design.md +++ b/docs/specs/2026-03-20-dag-aligned-improvements-design.md @@ -2,7 +2,7 @@ **Date**: 2026-03-20 **Version**: v2.6.0 -**Status**: DRAFT +**Status**: IMPLEMENTED ## Summary From d43cd7ddb7f0269e003bfe1d4b709f3ddb720c5d Mon Sep 17 00:00:00 2001 From: ducdmdev Date: Fri, 20 Mar 2026 10:11:10 +0700 Subject: [PATCH 18/22] fix: use epoch seconds for staleness comparison in detect-resume.sh String comparison of ISO 8601 timestamps fails across timezones (git log returns local TZ, completed_at may be UTC). Convert both to epoch seconds via date command for correct comparison. --- scripts/detect-resume.sh | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/scripts/detect-resume.sh b/scripts/detect-resume.sh index 304288e..66a955e 100755 --- a/scripts/detect-resume.sh +++ b/scripts/detect-resume.sh @@ -74,8 +74,11 @@ for graph_file in "${SORTED_GRAPHS[@]}"; do [ -z "$ofile" ] && continue FULL_PATH="$CWD/$ofile" if [ -f "$FULL_PATH" ]; then - FILE_DATE=$(cd "$CWD" && git log -1 --format=%cI -- "$ofile" 2>/dev/null) - if [ -n "$FILE_DATE" ] && [[ "$FILE_DATE" > "$COMPLETED_AT" ]]; then + # Use epoch seconds for comparison to handle timezone differences + # (git log returns local TZ, completed_at may be UTC) + FILE_EPOCH=$(cd "$CWD" && git log -1 --format=%ct -- "$ofile" 2>/dev/null) + COMPLETED_EPOCH=$(date -j -f "%Y-%m-%dT%H:%M:%SZ" "$COMPLETED_AT" "+%s" 2>/dev/null || date -d "$COMPLETED_AT" "+%s" 2>/dev/null) + if [ -n "$FILE_EPOCH" ] && [ -n "$COMPLETED_EPOCH" ] && [ "$FILE_EPOCH" -gt "$COMPLETED_EPOCH" ]; then IS_STALE=true STALE_LIST="${STALE_LIST} Completed (stale): $ID ($SUBJECT) — $ofile modified after completion\n" break From 1f69e063e85f627c4444e64f9bd21750f423231c Mon Sep 17 00:00:00 2001 From: ducdmdev Date: Fri, 20 Mar 2026 10:13:32 +0700 Subject: [PATCH 19/22] fix: address code review findings - detect-resume.sh: add "missing" file classification for deleted outputs - detect-resume.sh: use printf %b to prevent format-specifier injection - README.md: update hook count from "Five" to "Eight" --- README.md | 2 +- scripts/detect-resume.sh | 30 +++++++++++++++++------------- 2 files changed, 18 insertions(+), 14 deletions(-) diff --git a/README.md b/README.md index e3d6b78..cd7adb7 100644 --- a/README.md +++ b/README.md @@ -227,7 +227,7 @@ CHECKPOINT #N: intermediate results, artifacts, ready_for=[task IDs] ## Hooks -Five hooks enforce team discipline automatically: +Eight hooks enforce team discipline and provide DAG-aware coordination: ### TaskCompleted diff --git a/scripts/detect-resume.sh b/scripts/detect-resume.sh index 66a955e..efc2ed3 100755 --- a/scripts/detect-resume.sh +++ b/scripts/detect-resume.sh @@ -73,16 +73,20 @@ for graph_file in "${SORTED_GRAPHS[@]}"; do while IFS= read -r ofile; do [ -z "$ofile" ] && continue FULL_PATH="$CWD/$ofile" - if [ -f "$FULL_PATH" ]; then - # Use epoch seconds for comparison to handle timezone differences - # (git log returns local TZ, completed_at may be UTC) - FILE_EPOCH=$(cd "$CWD" && git log -1 --format=%ct -- "$ofile" 2>/dev/null) - COMPLETED_EPOCH=$(date -j -f "%Y-%m-%dT%H:%M:%SZ" "$COMPLETED_AT" "+%s" 2>/dev/null || date -d "$COMPLETED_AT" "+%s" 2>/dev/null) - if [ -n "$FILE_EPOCH" ] && [ -n "$COMPLETED_EPOCH" ] && [ "$FILE_EPOCH" -gt "$COMPLETED_EPOCH" ]; then - IS_STALE=true - STALE_LIST="${STALE_LIST} Completed (stale): $ID ($SUBJECT) — $ofile modified after completion\n" - break - fi + if [ ! -f "$FULL_PATH" ]; then + # Output file was deleted — classify as missing + IS_STALE=true + STALE_LIST="${STALE_LIST} Completed (missing): $ID ($SUBJECT) — $ofile no longer exists\n" + break + fi + # Use epoch seconds for comparison to handle timezone differences + # (git log returns local TZ, completed_at may be UTC) + FILE_EPOCH=$(cd "$CWD" && git log -1 --format=%ct -- "$ofile" 2>/dev/null) + COMPLETED_EPOCH=$(date -j -f "%Y-%m-%dT%H:%M:%SZ" "$COMPLETED_AT" "+%s" 2>/dev/null || date -d "$COMPLETED_AT" "+%s" 2>/dev/null) + if [ -n "$FILE_EPOCH" ] && [ -n "$COMPLETED_EPOCH" ] && [ "$FILE_EPOCH" -gt "$COMPLETED_EPOCH" ]; then + IS_STALE=true + STALE_LIST="${STALE_LIST} Completed (stale): $ID ($SUBJECT) — $ofile modified after completion\n" + break fi done <<< "$OUTPUT_FILES" fi @@ -105,9 +109,9 @@ for graph_file in "${SORTED_GRAPHS[@]}"; do echo "" echo "Resumable workspace found: $REL_PATH/" echo " Tasks: $COMPLETED/$TOTAL completed, $REMAINING remaining" - [ -n "$VALID_LIST" ] && printf "$VALID_LIST" - [ -n "$STALE_LIST" ] && printf "$STALE_LIST" - [ -n "$REMAINING_LIST" ] && printf "$REMAINING_LIST" + [ -n "$VALID_LIST" ] && printf "%b" "$VALID_LIST" + [ -n "$STALE_LIST" ] && printf "%b" "$STALE_LIST" + [ -n "$REMAINING_LIST" ] && printf "%b" "$REMAINING_LIST" # Show remaining critical path if available CP=$(echo "$GRAPH" | jq -r ' From 9a87a3c3e6204bd0b8f4116589cae5d2674dac70 Mon Sep 17 00:00:00 2001 From: ducdmdev Date: Fri, 20 Mar 2026 10:16:38 +0700 Subject: [PATCH 20/22] fix: address critical code review findings compute-critical-path.sh: - CRITICAL: add cycle detection pass before DFS (prevents infinite recursion) - HIGH: add null guard for non-existent dependency references - HIGH: skip NEXT_CRITICAL in blocked-task loop (prevents duplicate warnings) - LOW: fix "all parallel" message when all tasks complete shared-phases.md: - Add 3 new hooks to Hooks inventory section --- docs/shared-phases.md | 3 +++ scripts/compute-critical-path.sh | 45 +++++++++++++++++++++++++------- 2 files changed, 39 insertions(+), 9 deletions(-) diff --git a/docs/shared-phases.md b/docs/shared-phases.md index 20c34fe..d6b005c 100644 --- a/docs/shared-phases.md +++ b/docs/shared-phases.md @@ -46,6 +46,9 @@ This plugin registers hooks at the plugin level via `hooks/hooks.json`. They enf - **SessionStart(compact)** (`scripts/recover-context.sh`): After context compaction, automatically outputs active workspace paths and recovery instructions. Non-blocking. - **PreToolUse(Write|Edit)** (`scripts/check-file-ownership.sh`): Enforces file ownership via `file-locks.json`. Warn-then-block: first violation warns, second blocks. Workspace files (`.agent-team/`) always allowed. Requires `jq`. - **SubagentStart / SubagentStop** (`scripts/track-teammate-lifecycle.sh`): Logs teammate spawn and stop events to `.agent-team/{team}/events.log`. Non-blocking. +- **TaskCompleted** (`scripts/compute-critical-path.sh`): After each task completion, recomputes the critical path from `task-graph.json` and outputs the remaining critical chain. Warns about blocked critical-path tasks. Non-blocking. +- **TaskCompleted** (`scripts/check-integration-point.sh`): Detects when all upstream tasks of a convergence point complete. Nudges the lead to verify interface compatibility before the downstream task starts. Non-blocking. +- **SessionStart** (`scripts/detect-resume.sh`): On every session start, scans for incomplete workspaces with `task-graph.json`. Validates completed task staleness via git timestamps and outputs resume context. Non-blocking. All hooks exit 0 (allow) if their dependencies are missing — they degrade gracefully. Hook paths use `${CLAUDE_PLUGIN_ROOT}`. diff --git a/scripts/compute-critical-path.sh b/scripts/compute-critical-path.sh index d339dfd..4fd2670 100755 --- a/scripts/compute-critical-path.sh +++ b/scripts/compute-critical-path.sh @@ -39,23 +39,46 @@ fi REMAINING=$(echo "$GRAPH" | jq '[.nodes | to_entries[] | select(.value.status != "completed")] | length') if [ "$REMAINING" -eq 0 ]; then - echo "No critical path — all remaining tasks can run in parallel." >&2 + echo "All tasks completed — no critical path to display." >&2 + exit 0 +fi + +# Cycle detection: check for cycles before computing critical path. +# A cycle would cause infinite recursion in the DFS. +HAS_CYCLE=$(echo "$GRAPH" | jq ' + def has_cycle(id; visited): + if (visited | index(id)) then true + elif (.nodes[id] == null) then false + else + . as $g | + any(.nodes[id].depends_on[]; . as $dep | $g | has_cycle($dep; visited + [id])) + end; + . as $root | + any(.nodes | keys[]; . as $k | $root | has_cycle($k; [])) +' 2>/dev/null) + +if [ "$HAS_CYCLE" = "true" ]; then + echo "Warning: Cycle detected in task-graph.json dependency graph. Critical path cannot be computed. Fix the cycle per Circular Dependency Detection in coordination-advanced.md." >&2 exit 0 fi # DFS longest path computation via jq # For each remaining node, compute depth = 1 + max(depth of remaining dependencies) -# Cycle guard: track visited nodes +# Null guard: treat references to non-existent nodes as leaf nodes (depth 0) # Key jq scoping note: inside recursive calls, we must capture the dep ID with # `. as $dep_id | $root | depth($dep_id; ...)` to preserve the graph as `.` context. CRITICAL_PATH=$(echo "$GRAPH" | jq -r ' def depth(id; visited): if (visited | index(id)) then 0 + elif (.nodes[id] == null) then 0 elif (.nodes[id].status == "completed") then 0 elif (.nodes[id].depends_on | length == 0) then 1 else . as $g | - [.nodes[id].depends_on[] | select($g.nodes[.].status != "completed") | . as $dep_id | $g | depth($dep_id; visited + [id])] | max + 1 + [.nodes[id].depends_on[] | select(. as $d | $g.nodes[$d] != null and $g.nodes[$d].status != "completed") | . as $dep_id | $g | depth($dep_id; visited + [id])] | + if length == 0 then 1 + else max + 1 + end end; . as $root | @@ -65,17 +88,19 @@ CRITICAL_PATH=$(echo "$GRAPH" | jq -r ' if length == 0 then empty else .[0] as $deepest | - def trace_path(id): - if ($root.nodes[id].status == "completed") then [] + def trace_path(id; visited): + if (visited | index(id)) then [] + elif ($root.nodes[id] == null) then [] + elif ($root.nodes[id].status == "completed") then [] else - [$root.nodes[id].depends_on[] | select($root.nodes[.].status != "completed")] as $remaining_deps | + [$root.nodes[id].depends_on[] | select(. as $d | $root.nodes[$d] != null and $root.nodes[$d].status != "completed")] as $remaining_deps | if ($remaining_deps | length) == 0 then [id] else ([$remaining_deps[] | . as $dep_id | $root | {id: $dep_id, depth: depth($dep_id; [])}] | sort_by(-.depth, .id) | .[0].id) as $next | - trace_path($next) + [id] + trace_path($next; visited + [id]) + [id] end end; - $root | trace_path($deepest.id) | join(" → ") + $root | trace_path($deepest.id; []) | join(" → ") end ' 2>/dev/null) @@ -119,10 +144,12 @@ else echo "Task completed (not on critical path). Critical path unchanged: $OLD_CP (length: $(echo "$GRAPH" | jq '.critical_path_length'))" >&2 fi -# Check for any blocked tasks on the computed critical path +# Check for any blocked tasks on the computed critical path (only if not already warned about NEXT_CRITICAL) if [ -n "$CRITICAL_PATH" ]; then CP_NODES=$(echo "$CRITICAL_PATH" | tr '→' '\n' | sed 's/ //g' | grep '.') for cp_node in $CP_NODES; do + # Skip NEXT_CRITICAL — already handled above + [ "$cp_node" = "$NEXT_CRITICAL" ] && continue NODE_STATUS=$(echo "$GRAPH" | jq -r --arg id "$cp_node" '.nodes[$id].status // "unknown"') if [ "$NODE_STATUS" = "blocked" ]; then BLOCKED_BY=$(echo "$GRAPH" | jq -r --arg id "$cp_node" ' From 4bb89a5796ecd3673e479b458b6561259d274e58 Mon Sep 17 00:00:00 2001 From: ducdmdev Date: Fri, 20 Mar 2026 10:21:26 +0700 Subject: [PATCH 21/22] fix: add missing demo scripts to README Plugin Structure tree --- README.md | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index cd7adb7..a5c87d3 100644 --- a/README.md +++ b/README.md @@ -322,7 +322,9 @@ agent-team-plugin/ │ ├── merge-worktrees.sh # Worktree merge in Phase 5 │ ├── compute-critical-path.sh # ComputeCriticalPath hook │ ├── detect-resume.sh # DetectResume hook -│ └── check-integration-point.sh # CheckIntegrationPoint hook +│ ├── check-integration-point.sh # CheckIntegrationPoint hook +│ ├── record-demo.sh # Demo recording utility +│ └── generate-demo-cast.sh # Demo asciicast generator ├── skills/ │ ├── agent-team/ │ │ └── SKILL.md # Hybrid/catch-all orchestrator From 28ccdff08e3512a4c63d1f57a381100933dabeb5 Mon Sep 17 00:00:00 2001 From: ducdmdev Date: Fri, 20 Mar 2026 10:27:50 +0700 Subject: [PATCH 22/22] fix: sync documentation after audit MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - README.md: fix hook count "Eight" → "Nine" - CLAUDE.md: fix test count "9 files (78 assertions)" → "12 files (145 assertions)" - CLAUDE.md: fix "Adding a New Hook" to reference shared-phases.md (not SKILL.md) - coordination-patterns.md: sync Lead Processing table with shared-phases.md (add task-graph.json update to COMPLETED, add PROGRESS/CHECKPOINT/integration rows) - report-format.md: fix stale "Phase 5, step 7" reference - shared-phases.md: fix "critical chain" → "critical path" terminology (2 occurrences) --- CLAUDE.md | 4 ++-- README.md | 2 +- docs/coordination-patterns.md | 5 ++++- docs/report-format.md | 2 +- docs/shared-phases.md | 4 ++-- 5 files changed, 10 insertions(+), 7 deletions(-) diff --git a/CLAUDE.md b/CLAUDE.md index f597f68..85b3d18 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -96,7 +96,7 @@ chore: maintenance (CI, dependencies) bash tests/run-tests.sh ``` -Runs 9 test files (78 assertions) covering all hooks and plugin structure. +Runs 12 test files (145 assertions) covering all hooks and plugin structure. ### Validate Plugin @@ -133,7 +133,7 @@ Nine hook entries registered in `hooks/hooks.json`: 1. Add the script to `scripts/` 2. Make it executable 3. Register it in `hooks/hooks.json` using `${CLAUDE_PLUGIN_ROOT}/scripts/your-script.sh` -4. Document in SKILL.md Hooks section and README +4. Document in shared-phases.md Hooks section and README 5. Test: run `claude plugin validate .` then test manually in a team session ### Adding a New Teammate Role diff --git a/README.md b/README.md index a5c87d3..6b07f54 100644 --- a/README.md +++ b/README.md @@ -227,7 +227,7 @@ CHECKPOINT #N: intermediate results, artifacts, ready_for=[task IDs] ## Hooks -Eight hooks enforce team discipline and provide DAG-aware coordination: +Nine hooks enforce team discipline and provide DAG-aware coordination: ### TaskCompleted diff --git a/docs/coordination-patterns.md b/docs/coordination-patterns.md index 4dfea77..04ee0c7 100644 --- a/docs/coordination-patterns.md +++ b/docs/coordination-patterns.md @@ -37,10 +37,13 @@ When receiving structured messages: | Prefix | Lead Action | |--------|--------------| | STARTING | Update `tasks.md` status to `in_progress`, add note | -| COMPLETED | Update `tasks.md` status to `completed`, add file list and notes. Check: does this unblock other tasks? If yes, message the dependent teammate | +| COMPLETED | Update `tasks.md` status to `completed`, add file list and notes. Update `task-graph.json`: set node status to `completed`, record `completed_at` and `output_files`. **Self-check**: read `task-graph.json` back to verify valid JSON. Check: does this unblock other tasks? If yes, message the dependent teammate. The `compute-critical-path.sh` hook will output the updated critical path. | | BLOCKED | Add row to `issues.md` immediately. Acknowledge the teammate. Route to resolution | | HANDOFF | Extract key details, forward to dependent teammate with actionable context. Log in `progress.md` Handoffs | | QUESTION | Check if answer is in workspace files. If yes, answer with file reference. If no, investigate | +| PROGRESS | Note milestone in `tasks.md` Notes column. If percent indicates near-completion, no action needed. If stalled, trigger Deadline Escalation | +| CHECKPOINT | If `ready_for` lists task IDs, forward checkpoint details to dependent teammate. Log in `progress.md` Handoffs | +| (hook: integration checkpoint) | Read the nudge from `check-integration-point.sh`. Before unblocking the convergence task, verify interface compatibility between upstream outputs. If compatible, message the convergence task owner to proceed. If unclear, log in `issues.md` as medium severity. Log checkpoint in `progress.md` Decision Log. | ### Shared Workspace as Bulletin Board diff --git a/docs/report-format.md b/docs/report-format.md index 550b032..69c3ffe 100644 --- a/docs/report-format.md +++ b/docs/report-format.md @@ -13,7 +13,7 @@ The final report is a persistent artifact generated at completion. It lives in t `.agent-team/{team-name}/report.md` (relative to project root) -This file is generated during Phase 5, step 7. It is the last file written before shutdown. +This file is generated during the archetype-specific Phase 5 sequence, after the completion gate and before the remediation gate. It is the last major artifact written before shutdown. ## Template diff --git a/docs/shared-phases.md b/docs/shared-phases.md index d6b005c..a98d2ec 100644 --- a/docs/shared-phases.md +++ b/docs/shared-phases.md @@ -46,7 +46,7 @@ This plugin registers hooks at the plugin level via `hooks/hooks.json`. They enf - **SessionStart(compact)** (`scripts/recover-context.sh`): After context compaction, automatically outputs active workspace paths and recovery instructions. Non-blocking. - **PreToolUse(Write|Edit)** (`scripts/check-file-ownership.sh`): Enforces file ownership via `file-locks.json`. Warn-then-block: first violation warns, second blocks. Workspace files (`.agent-team/`) always allowed. Requires `jq`. - **SubagentStart / SubagentStop** (`scripts/track-teammate-lifecycle.sh`): Logs teammate spawn and stop events to `.agent-team/{team}/events.log`. Non-blocking. -- **TaskCompleted** (`scripts/compute-critical-path.sh`): After each task completion, recomputes the critical path from `task-graph.json` and outputs the remaining critical chain. Warns about blocked critical-path tasks. Non-blocking. +- **TaskCompleted** (`scripts/compute-critical-path.sh`): After each task completion, recomputes the critical path from `task-graph.json` and outputs the remaining critical path. Warns about blocked critical-path tasks. Non-blocking. - **TaskCompleted** (`scripts/check-integration-point.sh`): Detects when all upstream tasks of a convergence point complete. Nudges the lead to verify interface compatibility before the downstream task starts. Non-blocking. - **SessionStart** (`scripts/detect-resume.sh`): On every session start, scans for incomplete workspaces with `task-graph.json`. Validates completed task staleness via git timestamps and outputs resume context. Non-blocking. @@ -424,7 +424,7 @@ The critical path determines total execution time. The `compute-critical-path.sh - **BLOCKED on critical path** → resolve immediately (highest-priority coordination action) - **BLOCKED on non-critical path** → resolve normally (slippage has slack) -- **Teammate idle on critical path** → reassign work to keep the critical chain moving +- **Teammate idle on critical path** → reassign work to keep the critical path moving - **Teammate idle on non-critical path** → lower priority, consider assigning critical-path support tasks After every task completion, read the hook output. If the critical path shifted (a previously non-critical chain is now longest), update `task-graph.json` and the ★ markers in `tasks.md`.