diff --git a/.claude-plugin/marketplace.json b/.claude-plugin/marketplace.json index 162b533..06b1be2 100644 --- a/.claude-plugin/marketplace.json +++ b/.claude-plugin/marketplace.json @@ -12,7 +12,7 @@ "name": "PACT", "source": "./pact-plugin", "description": "PACT Framework - VSM-enhanced orchestration with specialist agents and viability sensing", - "version": "2.2.0", + "version": "2.1.10", "author": { "name": "ProfSynapse" }, diff --git a/pact-plugin/.claude-plugin/plugin.json b/pact-plugin/.claude-plugin/plugin.json index 79e96b4..c4e1926 100644 --- a/pact-plugin/.claude-plugin/plugin.json +++ b/pact-plugin/.claude-plugin/plugin.json @@ -1,6 +1,6 @@ { "name": "PACT", - "version": "2.2.0", + "version": "2.1.10", "description": "PACT Framework - Prepare, Architect, Code, Test methodology with VSM-enhanced orchestration, specialist agents, and viability sensing for systematic AI-assisted development", "author": { "name": "ProfSynapse", diff --git a/pact-plugin/CLAUDE.md b/pact-plugin/CLAUDE.md index 8e7e24c..5b607d4 100644 --- a/pact-plugin/CLAUDE.md +++ b/pact-plugin/CLAUDE.md @@ -106,6 +106,36 @@ Delegate to `pact-memory-agent` with a clear intent: See **Always Run Agents in Background** for the mandatory `run_in_background=true` requirement. +### Task Lifecycle Management + +The orchestrator owns ALL Task tool calls (TaskCreate, TaskUpdate, TaskGet, TaskList). Agents do not have access to these tools. The orchestrator manages Task state based on agent lifecycle events. + +**Dispatch lifecycle**: + +| Event | Orchestrator Action | +|-------|---------------------| +| Before dispatching agent | `TaskCreate(subject="...")` to get `task_id` | +| Immediately after dispatch | `TaskUpdate(taskId, status="in_progress")` | +| Agent completes (handoff received) | `TaskUpdate(taskId, status="completed", metadata={...})` | +| Agent reports blocker | `TaskCreate blocker` then `TaskUpdate(taskId, addBlockedBy=[blocker_id])` | +| Agent emits algedonic signal | `TaskCreate algedonic` then amplify scope per protocol | + +**Key principle**: Agents communicate status through structured text (handoff format, blocker reports, algedonic signals). The orchestrator translates these into Task tool calls. + +#### Signal Task Handling + +**Blocker text received** (agent reports `BLOCKER: {description}`): +1. Create blocker Task: `TaskCreate(subject="Resolve: {desc}", metadata={"type": "blocker"})` +2. Block agent Task: `TaskUpdate(agent_task_id, addBlockedBy=[blocker_id])` +3. Triage via `/PACT:imPACT` + +**Algedonic text received** (agent reports `ALGEDONIC [HALT|ALERT]: {category}`): +1. Create algedonic Task: `TaskCreate(subject="[HALT|ALERT]: {category}", metadata={"type": "algedonic"})` +2. Amplify scope: + - **ALERT**: `TaskUpdate(current_phase_id, addBlockedBy=[algedonic_id])` + - **HALT**: `TaskUpdate(feature_task_id, addBlockedBy=[algedonic_id])` +3. Surface to user immediately (see Algedonic Signals protocol) + ### S3/S4 Operational Modes The orchestrator operates in two distinct modes. Being aware of which mode you're in improves decision-making. diff --git a/pact-plugin/agents/pact-architect.md b/pact-plugin/agents/pact-architect.md index 6f7c1c0..c28d982 100644 --- a/pact-plugin/agents/pact-architect.md +++ b/pact-plugin/agents/pact-architect.md @@ -4,9 +4,8 @@ description: | Use this agent to design system architectures: component diagrams, API contracts, data flows, and implementation guidelines. Use after preparation/research is complete. color: green -permissionMode: acceptEdits -skills: - - pact-task-tracking +tools: Read, Grep, Glob, Write, Bash +permissionMode: default --- You are πŸ›οΈ PACT Architect, a solution design specialist focusing on the Architect phase of the PACT framework. You handle the second phase of the Prepare, Architect, Code, Test (PACT), receiving research and documentation from the Prepare phase to create comprehensive architectural designs that guide implementation in the Code phase. diff --git a/pact-plugin/agents/pact-backend-coder.md b/pact-plugin/agents/pact-backend-coder.md index 3da378f..bec2b67 100644 --- a/pact-plugin/agents/pact-backend-coder.md +++ b/pact-plugin/agents/pact-backend-coder.md @@ -4,9 +4,8 @@ description: | Use this agent to implement backend code: server-side components, APIs, business logic, and data processing. Use after architectural specifications are ready. color: yellow +tools: Read, Grep, Glob, Edit, Write, Bash permissionMode: acceptEdits -skills: - - pact-task-tracking --- You are πŸ’» PACT Backend Coder, a server-side development specialist focusing on backend implementation during the Code phase of the Prepare, Architect, Code, Test (PACT) framework. diff --git a/pact-plugin/agents/pact-database-engineer.md b/pact-plugin/agents/pact-database-engineer.md index 546be95..15aeaec 100644 --- a/pact-plugin/agents/pact-database-engineer.md +++ b/pact-plugin/agents/pact-database-engineer.md @@ -4,9 +4,8 @@ description: | Use this agent to implement database solutions: schemas, optimized queries, data models, indexes, and data integrity. Use after architectural specifications are ready. color: orange +tools: Read, Grep, Glob, Edit, Write, Bash permissionMode: acceptEdits -skills: - - pact-task-tracking --- You are πŸ—„οΈ PACT Database Engineer, a data storage specialist focusing on database implementation during the Code phase of the PACT framework. diff --git a/pact-plugin/agents/pact-frontend-coder.md b/pact-plugin/agents/pact-frontend-coder.md index 99310fe..7285e08 100644 --- a/pact-plugin/agents/pact-frontend-coder.md +++ b/pact-plugin/agents/pact-frontend-coder.md @@ -4,9 +4,8 @@ description: | Use this agent to implement frontend code: responsive, accessible user interfaces with proper state management. Use after architectural specifications are ready. color: cyan +tools: Read, Grep, Glob, Edit, Write, Bash permissionMode: acceptEdits -skills: - - pact-task-tracking --- You are **🎨 PACT Frontend Coder**, a client-side development specialist focusing on frontend implementation during the Code phase of the PACT framework. diff --git a/pact-plugin/agents/pact-memory-agent.md b/pact-plugin/agents/pact-memory-agent.md index a538c3d..481e157 100644 --- a/pact-plugin/agents/pact-memory-agent.md +++ b/pact-plugin/agents/pact-memory-agent.md @@ -27,9 +27,8 @@ description: | Context recovery at session start benefits from memory agent's search and synthesis capabilities. color: purple +tools: Read, Grep, Glob, Edit, Write, Bash permissionMode: acceptEdits -skills: - - pact-task-tracking --- You are 🧠 PACT Memory Agent, a specialist in context preservation and memory management for the PACT framework. @@ -129,7 +128,7 @@ Always structure your output clearly: [Any follow-up actions needed] ``` -**AUTONOMY CHARTER** +# AUTONOMY CHARTER You have authority to: - Determine the appropriate search strategy for context recovery @@ -149,23 +148,14 @@ You must escalate when: See [algedonic.md](../protocols/algedonic.md) for signal format and full trigger list. -**HOW TO HANDLE BLOCKERS** - -If you run into a blocker, STOP what you're doing and report the blocker to the orchestrator, so they can take over and invoke `/PACT:imPACT`. - -Examples of blockers: -- Same error after multiple fixes -- Missing info needed to proceed -- Task goes beyond your specialty - -**DOMAIN-SPECIFIC BLOCKERS** +# HOW TO HANDLE BLOCKERS If you encounter issues with the memory system: 1. Check memory status with `get_status()` 2. Report specific error to orchestrator 3. Suggest fallback (e.g., manual context capture in docs/) -Common memory-specific issues: +**Common issues**: - Embedding model not available β†’ Falls back to keyword search - Database locked β†’ Retry after brief wait - No memories found β†’ Report and suggest saving initial context diff --git a/pact-plugin/agents/pact-n8n.md b/pact-plugin/agents/pact-n8n.md index a7c3065..e21aab0 100644 --- a/pact-plugin/agents/pact-n8n.md +++ b/pact-plugin/agents/pact-n8n.md @@ -4,9 +4,8 @@ description: | Use this agent to build, validate, or troubleshoot n8n workflows: webhooks, HTTP integrations, database workflows, AI agent workflows, and scheduled tasks. Requires n8n-mcp MCP server. color: red +tools: Read, Grep, Glob, Edit, Write, Bash permissionMode: acceptEdits -skills: - - pact-task-tracking --- You are n8n PACT n8n Workflow Specialist, a workflow automation expert focusing on building, validating, and deploying n8n workflows during the Code phase of the Prepare, Architect, Code, Test (PACT) framework. @@ -127,7 +126,7 @@ Provide: 5. **Validation Status**: Results of validation and any fixes applied 6. **Activation Status**: Whether workflow is active or draft -**HANDOFF** +# HANDOFF End with a structured handoff for the orchestrator: 1. **Produced**: Workflow created, key node configurations @@ -135,7 +134,7 @@ End with a structured handoff for the orchestrator: 3. **Areas of uncertainty**: Known limitations, edge cases (helps TEST phase) 4. **Open questions**: Anything unresolved -**AUTONOMY CHARTER** +# AUTONOMY CHARTER You have authority to: - Adjust workflow approach based on discoveries during implementation @@ -163,7 +162,7 @@ See [algedonic.md](../protocols/algedonic.md) for signal format and full trigger - "Simpler than expected" β€” Note in handoff; orchestrator may simplify remaining work - "More complex than expected" β€” Escalate if scope change >20%, or note for orchestrator -**BEFORE COMPLETING** +# BEFORE COMPLETING Before returning your final output to the orchestrator: @@ -176,6 +175,16 @@ Before returning your final output to the orchestrator: This ensures your workflow context persists across sessions and is searchable by future agents. +# HOW TO HANDLE BLOCKERS + +If you run into a blocker, STOP and report to the orchestrator for `/PACT:imPACT`: + +Examples of blockers: +- n8n-mcp MCP server unavailable +- Node type not found after multiple search attempts +- Validation errors that persist after 3+ fix attempts +- Required credentials not configured +- API rate limiting or connectivity issues # TEMPLATE DEPLOYMENT @@ -186,21 +195,3 @@ n8n_deploy_template({templateId: 2947, name: "My Custom Name"}) ``` Templates provide battle-tested starting points that you can customize. - -**HOW TO HANDLE BLOCKERS** - -If you run into a blocker, STOP what you're doing and report the blocker to the orchestrator, so they can take over and invoke `/PACT:imPACT`. - -Examples of blockers: -- Same error after multiple fixes -- Missing info needed to proceed -- Task goes beyond your specialty - -**DOMAIN-SPECIFIC BLOCKERS** - -Examples of n8n-specific blockers to report: -- n8n-mcp MCP server unavailable -- Node type not found after multiple search attempts -- Validation errors that persist after 3+ fix attempts -- Required credentials not configured -- API rate limiting or connectivity issues diff --git a/pact-plugin/agents/pact-preparer.md b/pact-plugin/agents/pact-preparer.md index 6fe2ffe..441f90e 100644 --- a/pact-plugin/agents/pact-preparer.md +++ b/pact-plugin/agents/pact-preparer.md @@ -4,9 +4,8 @@ description: | Use this agent to research and gather documentation: API docs, best practices, code examples, and technical information for development. First phase of PACT. color: blue -permissionMode: acceptEdits -skills: - - pact-task-tracking +tools: Read, Grep, Glob, WebFetch, WebSearch, Bash, Write +permissionMode: default --- You are πŸ“š PACT Preparer, a documentation and research specialist focusing on the Prepare phase of software development within the PACT framework. You are an expert at finding, evaluating, and organizing technical documentation from authoritative sources. diff --git a/pact-plugin/agents/pact-test-engineer.md b/pact-plugin/agents/pact-test-engineer.md index 5bbb228..f75d040 100644 --- a/pact-plugin/agents/pact-test-engineer.md +++ b/pact-plugin/agents/pact-test-engineer.md @@ -4,9 +4,8 @@ description: | Use this agent to create and run tests: unit tests, integration tests, E2E tests, performance tests, and security tests. Use after code implementation is complete. color: magenta +tools: Read, Grep, Glob, Edit, Write, Bash permissionMode: acceptEdits -skills: - - pact-task-tracking --- You are πŸ§ͺ PACT Tester, an elite quality assurance specialist and test automation expert focusing on the Test phase of the Prepare, Architect, Code, and Test (PACT) software development framework. You possess deep expertise in test-driven development (TDD), behavior-driven development, and comprehensive testing methodologies across all levels of the testing pyramid. diff --git a/pact-plugin/commands/comPACT.md b/pact-plugin/commands/comPACT.md index 26e0e40..371b23f 100644 --- a/pact-plugin/commands/comPACT.md +++ b/pact-plugin/commands/comPACT.md @@ -12,25 +12,21 @@ Delegate this focused task within a single PACT domain: $ARGUMENTS ## Task Hierarchy -Create a simpler Task hierarchy than full orchestrate: +Create a lightweight Task hierarchy for single-domain work: ``` -1. TaskCreate: Feature task "{verb} {feature}" (single-domain work) +1. TaskCreate: Feature task β€” subject: "{verb} {feature}" (single-domain work) 2. Analyze: How many agents needed? 3. TaskCreate: Agent task(s) β€” direct children of feature + - subject: "{agent-type}: {work-description}" 4. TaskUpdate: Feature task addBlockedBy = [all agent IDs] -5. Dispatch agents concurrently with task IDs -6. Monitor via TaskList until all agents complete -7. TaskUpdate: Feature task status = "completed" +5. Dispatch agents concurrently (mark each in_progress immediately after dispatch) +6. Monitor via TaskList until all agents complete (handoffs received) +7. TaskUpdate: Agent tasks completed (extract metadata from handoffs) +8. TaskUpdate: Feature task completed ``` -**Example structure:** -``` -[Feature] "Fix 3 backend bugs" (blockedBy: agent1, agent2, agent3) -β”œβ”€β”€ [Agent] "backend-coder: fix bug A" -β”œβ”€β”€ [Agent] "backend-coder: fix bug B" -└── [Agent] "backend-coder: fix bug C" -``` +**Graceful degradation**: If any Task tool call fails, log a warning and continue. Task integration enhances PACT but should never block it. --- @@ -196,23 +192,11 @@ Task: [user's task description] --- -## Signal Monitoring - -Check TaskList for blocker/algedonic signals: -- After each agent dispatch -- When agent reports completion -- On any unexpected agent stoppage - -On signal detected: Follow Signal Task Handling in CLAUDE.md. - ---- - ## After Specialist Completes 1. **Receive handoff** from specialist(s) 2. **Run tests** β€” verify work passes. If tests fail β†’ return to specialist for fixes before committing. -3. **TaskUpdate**: Feature task status = "completed" -4. **Create atomic commit(s)** β€” stage and commit before proceeding +3. **Create atomic commit(s)** β€” stage and commit before proceeding **Next steps** (user decides): - Done β†’ work is committed @@ -259,3 +243,29 @@ During comPACT execution, if you discover the task is more complex than expected **Conversely**, if the specialist reports the task is simpler than expected: - Note in handoff to orchestrator - Complete the task; orchestrator may simplify remaining work + +--- + +## Signal Monitoring + +Check TaskList for blocker/algedonic signals: +- After each agent dispatch +- When agent reports completion +- On any unexpected agent stoppage + +On signal detected: Follow Task Lifecycle Management in CLAUDE.md. + +--- + +## Agent Prompt Language + +When dispatching agents, include this block in the agent prompt: + +``` +**Blocker/Signal Protocol**: +- If you hit a blocker, STOP work immediately and report: "BLOCKER: {description}" +- If you detect a viability threat (security, data, ethics), STOP immediately and report: + "⚠️ ALGEDONIC [HALT|ALERT]: {category} β€” {description}" +- Do NOT attempt workarounds for blockers. Do NOT continue work after emitting algedonic signals. +- Always end your response with a structured HANDOFF, even if incomplete. +``` diff --git a/pact-plugin/commands/imPACT.md b/pact-plugin/commands/imPACT.md index 8cc6b3c..373e816 100644 --- a/pact-plugin/commands/imPACT.md +++ b/pact-plugin/commands/imPACT.md @@ -6,24 +6,6 @@ You hit a blocker: $ARGUMENTS --- -## Task Operations - -imPACT operates on existing blocker Tasks created by agents: - -``` -1. TaskGet(blocker_id) β€” understand the blocker context -2. Triage: redo prior phase? need specialist? need user? -3. On resolution path chosen: - - If delegating: TaskCreate resolution agent task - - If self-resolving: proceed directly -4. On resolution complete: TaskUpdate(blocker_id, status="completed") -5. Blocked agent task is now unblocked -``` - -**Note**: Agents create blocker Tasks and block themselves via `addBlockedBy`. When the blocker is resolved (marked completed), the agent's task becomes unblocked. - ---- - ## Core Principle: Diagnose, Don't Fix **Your role is triage, not implementation.** Even if you know exactly what's wrong and how to fix it: @@ -117,3 +99,29 @@ If the blocker reveals that a sub-task is more complex than expected and needs i ``` /PACT:rePACT backend "implement the OAuth2 token refresh that's blocking us" ``` + +--- + +## Task Operations + +imPACT operates on blocker Tasks that the orchestrator already created from agent text reports. Agents do NOT create Tasks themselves β€” the orchestrator translates agent `BLOCKER:` text into Task state. + +**Flow**: +``` +1. Agent reported: "BLOCKER: {description}" (text, not Task tool) +2. Orchestrator already created blocker Task: + TaskCreate(subject="Resolve: {desc}", metadata={"type": "blocker"}) +3. Orchestrator blocked the agent Task: + TaskUpdate(agent_task_id, addBlockedBy=[blocker_id]) +4. imPACT receives the blocker_id +5. TaskGet(blocker_id) β€” understand the blocker context +6. Triage: redo prior phase? need specialist? need user? +7. On resolution path chosen: + - If delegating: TaskCreate resolution agent task, dispatch + - If self-resolving: proceed directly +8. On resolution complete: + TaskUpdate(blocker_id, status="completed") +9. Blocked agent task is now unblocked +``` + +**Graceful degradation**: If TaskGet fails, proceed with triage using the blocker description from the agent's text report. diff --git a/pact-plugin/commands/orchestrate.md b/pact-plugin/commands/orchestrate.md index 2fa1f5a..94d3898 100644 --- a/pact-plugin/commands/orchestrate.md +++ b/pact-plugin/commands/orchestrate.md @@ -6,38 +6,6 @@ Orchestrate specialist PACT agents through the PACT workflow to address: $ARGUME --- -## Task Hierarchy - -Create the full Task hierarchy upfront for workflow visibility: - -``` -1. TaskCreate: Feature task "{verb} {feature}" -2. TaskCreate: Phase tasks (all upfront): - - "PREPARE: {feature-slug}" - - "ARCHITECT: {feature-slug}" - - "CODE: {feature-slug}" - - "TEST: {feature-slug}" -3. TaskUpdate: Set phase-to-phase blockedBy chain: - - ARCHITECT blockedBy PREPARE - - CODE blockedBy ARCHITECT - - TEST blockedBy CODE -``` - -For each phase execution: -``` -a. TaskUpdate: phase status = "in_progress" -b. Analyze work needed (QDCL for CODE) -c. TaskCreate: agent task(s) as children of phase -d. TaskUpdate: next phase addBlockedBy = [agent IDs] -e. Dispatch agents with task IDs in their prompts -f. Monitor via TaskList until agents complete -g. TaskUpdate: phase status = "completed" -``` - -**Skipped phases**: Create the phase Task, then immediately mark `completed` with metadata noting skip reason. - ---- - ## S3/S4 Mode Awareness This command primarily operates in **S3 mode** (operational control)β€”executing the plan and coordinating agents. However, mode transitions are important: @@ -96,6 +64,41 @@ See [algedonic.md](../protocols/algedonic.md) for signal format and full protoco --- +## Task Hierarchy + +At workflow start, create the full Task hierarchy upfront. This provides visibility into the entire workflow before execution begins. + +``` +1. TaskCreate: Feature task β€” subject: "{verb} {feature}" +2. TaskCreate: Phase tasks: + - "PREPARE: {feature-slug}" + - "ARCHITECT: {feature-slug}" + - "CODE: {feature-slug}" + - "TEST: {feature-slug}" +3. TaskUpdate: Set phase-to-phase blockedBy chain: + - ARCHITECT blockedBy [PREPARE] + - CODE blockedBy [ARCHITECT] + - TEST blockedBy [CODE] +4. For each phase execution: + a. TaskUpdate: phase status = "in_progress" + b. Analyze work needed (QDCL for CODE) + c. TaskCreate: agent task(s) β€” subject: "{agent-type}: {work-description}" + d. Dispatch agents (mark agent tasks in_progress immediately after dispatch) + e. Monitor via TaskList until agents complete (handoff received) + f. TaskUpdate: agent tasks completed (extract metadata from handoff) + g. TaskUpdate: phase status = "completed" +5. Skipped phases: TaskUpdate status = "completed" with description noting skip reason +``` + +**Naming convention**: +- Feature: `"{verb} {feature}"` (e.g., "Implement user authentication") +- Phase: `"{PHASE}: {feature-slug}"` (e.g., "CODE: auth-feature") +- Agent: `"{agent-type}: {work-description}"` (e.g., "pact-backend-coder: auth endpoint") + +**Graceful degradation**: If any Task tool call fails, log a warning and continue without Task tracking for that item. Task integration enhances PACT but should never block it. + +--- + ## Before Starting ### Task Variety Assessment @@ -406,6 +409,20 @@ If a sub-task emerges that is too complex for a single specialist invocation: --- +## After All Phases Complete + +> **S5 Policy Checkpoint (Pre-PR)**: Before creating PR, verify: "Do all tests pass? Is system integrity maintained? Have S5 non-negotiables been respected throughout?" + +1. **Update plan status** (if plan exists): IN_PROGRESS β†’ IMPLEMENTED +2. **Verify all work is committed** β€” CODE and TEST phase commits should already exist; if any uncommitted changes remain, commit them now +3. **Run `/PACT:peer-review`** to create PR and get multi-agent review +4. **Present review summary and stop** β€” orchestrator never merges (S5 policy) +5. **S4 Retrospective** (after user decides): Briefly noteβ€”what worked well? What should we adapt for next time? +6. **High-variety audit trail** (variety 10+ only): Delegate to `pact-memory-agent` to save key orchestration decisions, S3/S4 tensions resolved, and lessons learned +7. **TaskUpdate**: Feature task status = "completed" + +--- + ## Signal Monitoring Check TaskList for blocker/algedonic signals: @@ -413,18 +430,19 @@ Check TaskList for blocker/algedonic signals: - When agent reports completion - On any unexpected agent stoppage -On signal detected: Follow Signal Task Handling in CLAUDE.md. +On signal detected: Follow Task Lifecycle Management in CLAUDE.md. --- -## After All Phases Complete +## Agent Prompt Language -> **S5 Policy Checkpoint (Pre-PR)**: Before creating PR, verify: "Do all tests pass? Is system integrity maintained? Have S5 non-negotiables been respected throughout?" +When dispatching agents in any phase, include this block in the agent prompt: -1. **Update plan status** (if plan exists): IN_PROGRESS β†’ IMPLEMENTED -2. **TaskUpdate**: Feature task status = "completed" (all phases done) -3. **Verify all work is committed** β€” CODE and TEST phase commits should already exist; if any uncommitted changes remain, commit them now -4. **Run `/PACT:peer-review`** to create PR and get multi-agent review -5. **Present review summary and stop** β€” orchestrator never merges (S5 policy) -6. **S4 Retrospective** (after user decides): Briefly noteβ€”what worked well? What should we adapt for next time? -7. **High-variety audit trail** (variety 10+ only): Delegate to `pact-memory-agent` to save key orchestration decisions, S3/S4 tensions resolved, and lessons learned +``` +**Blocker/Signal Protocol**: +- If you hit a blocker, STOP work immediately and report: "BLOCKER: {description}" +- If you detect a viability threat (security, data, ethics), STOP immediately and report: + "⚠️ ALGEDONIC [HALT|ALERT]: {category} β€” {description}" +- Do NOT attempt workarounds for blockers. Do NOT continue work after emitting algedonic signals. +- Always end your response with a structured HANDOFF, even if incomplete. +``` diff --git a/pact-plugin/commands/peer-review.md b/pact-plugin/commands/peer-review.md index 9d53ff8..c835d4a 100644 --- a/pact-plugin/commands/peer-review.md +++ b/pact-plugin/commands/peer-review.md @@ -8,50 +8,6 @@ Review the current work: $ARGUMENTS 2. Create a PR if one doesn't exist 3. Review the PR ---- - -## Task Hierarchy - -Create a review Task hierarchy: - -``` -1. TaskCreate: Review task "Review: {feature}" -2. Analyze PR: Which reviewers needed? -3. TaskCreate: Reviewer agent tasks (architect, test-engineer, domain specialists) -4. TaskUpdate: Review task addBlockedBy = [reviewer IDs] -5. Dispatch reviewers in parallel -6. Monitor until reviewers complete -7. Synthesize findings -8. If major issues: - a. TaskCreate: Remediation agent tasks - b. Dispatch, monitor until complete -9. TaskCreate: "User: review minor issues" step task -10. Present minor issues to user, record decisions in step metadata -11. If "fix now" decisions: - a. TaskCreate: Remediation agent tasks - b. Dispatch, monitor until complete -12. TaskCreate: "Awaiting merge decision" approval task -13. Present to user, await approval -14. On approval: TaskUpdate approval completed -15. TaskUpdate: Review task completed, metadata.artifact = PR URL -``` - -**Example structure:** -``` -[Review] "Review: user authentication" -β”œβ”€β”€ [Agent] "architect: design review" -β”œβ”€β”€ [Agent] "test-engineer: coverage review" -β”œβ”€β”€ [Agent] "backend-coder: implementation review" -β”œβ”€β”€ [Remediation] (dynamic, for major issues) -β”‚ └── [Agent] "fix: auth vulnerability" -β”œβ”€β”€ [Step] "User: review minor issues" -β”œβ”€β”€ [Remediation] (dynamic, for "fix now" minors) -β”‚ └── [Agent] "fix: input validation" -└── [Approval] "Awaiting merge decision" -``` - ---- - **PR Review Workflow** Pull request reviews should mirror real-world team practices where multiple reviewers sign off before merging. Invoke **at least 3 agents in parallel** to provide comprehensive review coverage: @@ -69,6 +25,41 @@ Select the domain coder based on PR focus: --- +## Task Hierarchy + +Create Task hierarchy for the review process: + +``` +1. TaskCreate: Review task β€” subject: "Review: {feature-slug}" +2. Analyze PR: Which reviewers needed? +3. TaskCreate: Reviewer agent tasks + - subject: "{agent-type}: review {feature-slug}" + - (architect, test-engineer, domain specialist(s)) +4. TaskUpdate: Review task addBlockedBy = [reviewer IDs] +5. Dispatch reviewers in parallel (mark in_progress immediately after dispatch) +6. Monitor until reviewers complete (handoffs received) +7. TaskUpdate: Reviewer tasks completed (extract metadata from handoffs) +8. Synthesize findings +9. If major/blocking issues found: + a. TaskCreate: Remediation agent tasks + b. Dispatch, monitor until complete + c. TaskUpdate: Remediation tasks completed +10. If minor/future items require fixes (user approved): + a. TaskCreate: Fix agent tasks + b. Dispatch, monitor until complete + c. TaskUpdate: Fix tasks completed +11. TaskCreate: "Awaiting merge decision" β€” approval task +12. Present to user, await approval +13. On approval: TaskUpdate approval task completed +14. TaskUpdate: Review task completed, metadata.artifact = PR URL +``` + +**Merge authorization boundary**: The orchestrator NEVER merges. Present findings, state merge readiness, then stop and wait for explicit user authorization. + +**Graceful degradation**: If any Task tool call fails, log a warning and continue. Task integration enhances PACT but should never block it. + +--- + ## Output Conciseness **Default: Concise output.** User sees synthesis, not each reviewer's full output restated. @@ -156,15 +147,30 @@ Select the domain coder based on PR focus: --- +**After user-authorized merge**: Run `/PACT:pin-memory` to update the project `CLAUDE.md` with the latest changes. + +--- + ## Signal Monitoring Check TaskList for blocker/algedonic signals: -- After each reviewer dispatch -- After each remediation dispatch +- After each agent dispatch (reviewers, remediation agents) +- When agent reports completion - On any unexpected agent stoppage -On signal detected: Follow Signal Task Handling in CLAUDE.md. +On signal detected: Follow Task Lifecycle Management in CLAUDE.md. --- -**After user-authorized merge**: Run `/PACT:pin-memory` to update the project `CLAUDE.md` with the latest changes. +## Agent Prompt Language + +When dispatching reviewer or remediation agents, include this block in the agent prompt: + +``` +**Blocker/Signal Protocol**: +- If you hit a blocker, STOP work immediately and report: "BLOCKER: {description}" +- If you detect a viability threat (security, data, ethics), STOP immediately and report: + "⚠️ ALGEDONIC [HALT|ALERT]: {category} β€” {description}" +- Do NOT attempt workarounds for blockers. Do NOT continue work after emitting algedonic signals. +- Always end your response with a structured HANDOFF, even if incomplete. +``` diff --git a/pact-plugin/commands/plan-mode.md b/pact-plugin/commands/plan-mode.md index c55351f..1a2f0ad 100644 --- a/pact-plugin/commands/plan-mode.md +++ b/pact-plugin/commands/plan-mode.md @@ -8,31 +8,6 @@ Create a comprehensive implementation plan for: $ARGUMENTS --- -## Task Hierarchy - -Create a planning Task hierarchy: - -``` -1. TaskCreate: Planning task "Plan: {feature}" -2. Analyze: Which specialists to consult? -3. TaskCreate: Consultation task(s) β€” one per specialist -4. TaskUpdate: Planning task addBlockedBy = [consultation IDs] -5. Dispatch specialists in parallel (planning-only mode) -6. Monitor until consultations complete -7. Synthesize β†’ write plan document -8. TaskUpdate: Planning task completed, metadata.artifact = plan path -``` - -**Example structure:** -``` -[Planning] "Plan: user authentication" (blockedBy: consult1, consult2, consult3) -β”œβ”€β”€ [Consult] "preparer: research auth patterns" -β”œβ”€β”€ [Consult] "architect: design auth service" -└── [Consult] "test-engineer: testing strategy" -``` - ---- - ## S4 Intelligence Function This command is the primary **S4 (Intelligence)** activity in PACT. While `/PACT:orchestrate` operates mainly in S3 mode (execution), `plan-mode` operates entirely in S4 mode: @@ -63,6 +38,27 @@ See [pact-variety.md](../protocols/pact-variety.md) for the Variety Management a --- +## Task Hierarchy + +Create Task hierarchy for planning consultation: + +``` +1. TaskCreate: Planning task β€” subject: "Plan: {feature-slug}" +2. Analyze: Which specialists to consult? +3. TaskCreate: Consultation task(s) + - subject: "{agent-type}: plan consultation for {feature-slug}" +4. TaskUpdate: Planning task addBlockedBy = [consultation IDs] +5. Dispatch specialists in parallel (mark in_progress immediately after dispatch) +6. Monitor until consultations complete (handoffs received) +7. TaskUpdate: Consultation tasks completed (extract metadata from handoffs) +8. Synthesize plan, write to docs/plans/ +9. TaskUpdate: Planning task completed, metadata.artifact = plan path +``` + +**Graceful degradation**: If any Task tool call fails, log a warning and continue. Task integration enhances PACT but should never block it. + +--- + ## Your Workflow ### Phase 0: Orchestrator Analysis @@ -231,18 +227,6 @@ After collecting all specialist outputs, use extended thinking to synthesize: ### Phase 3: Plan Output -**TaskUpdate**: Planning task completed with artifact path: -``` -TaskUpdate( - taskId=planning_task_id, - status="completed", - metadata={ - "artifact": "docs/plans/{feature-slug}-plan.md", - "summary": "Planning complete. Awaiting user approval." - } -) -``` - Save the synthesized plan to `docs/plans/{feature-slug}-plan.md`. **Handling existing plans**: @@ -498,4 +482,28 @@ After the user approves the plan: 3. If plan exists, use it as the implementation specification 4. Specialists receive relevant sections of the plan as context -**Task Linkage**: When `/PACT:orchestrate` runs, it checks for a completed Planning task matching the feature. If found, the plan artifact path from `metadata.artifact` is used to locate and reference the approved plan automatically. +--- + +## Signal Monitoring + +Check TaskList for blocker/algedonic signals: +- After each agent dispatch +- When agent reports completion +- On any unexpected agent stoppage + +On signal detected: Follow Task Lifecycle Management in CLAUDE.md. + +--- + +## Agent Prompt Language + +When dispatching consultation specialists, include this block in the agent prompt: + +``` +**Blocker/Signal Protocol**: +- If you hit a blocker, STOP work immediately and report: "BLOCKER: {description}" +- If you detect a viability threat (security, data, ethics), STOP immediately and report: + "⚠️ ALGEDONIC [HALT|ALERT]: {category} β€” {description}" +- Do NOT attempt workarounds for blockers. Do NOT continue work after emitting algedonic signals. +- Always end your response with a structured HANDOFF, even if incomplete. +``` diff --git a/pact-plugin/commands/rePACT.md b/pact-plugin/commands/rePACT.md index 965fcd3..8d65dfe 100644 --- a/pact-plugin/commands/rePACT.md +++ b/pact-plugin/commands/rePACT.md @@ -8,36 +8,6 @@ This command initiates a **nested Pβ†’Aβ†’Cβ†’T cycle** for a sub-task that is t --- -## Task Hierarchy - -Create a nested Task hierarchy as a child of the current context: - -``` -1. TaskCreate: Sub-feature task "{verb} {sub-feature}" (child of parent context) -2. TaskCreate: Nested phase tasks: - - "PREPARE: {sub-feature-slug}" - - "ARCHITECT: {sub-feature-slug}" - - "CODE: {sub-feature-slug}" - - "TEST: {sub-feature-slug}" -3. TaskUpdate: Set dependencies: - - Phase-to-phase blockedBy chain (same as orchestrate) - - Parent task addBlockedBy = [sub-feature task] -4. Execute nested Pβ†’Aβ†’Cβ†’T cycle -5. On completion: Parent task unblocked -``` - -**Example structure:** -``` -[Feature] "Implement user auth" (parent, blockedBy: sub-feature) -└── [Sub-Feature] "Implement OAuth2 token refresh" - β”œβ”€β”€ [Phase] "PREPARE: oauth2-token-refresh" - β”œβ”€β”€ [Phase] "ARCHITECT: oauth2-token-refresh" - β”œβ”€β”€ [Phase] "CODE: oauth2-token-refresh" - └── [Phase] "TEST: oauth2-token-refresh" -``` - ---- - ## When to Use rePACT Use `/PACT:rePACT` when: @@ -100,6 +70,32 @@ This runs a mini-orchestration: --- +## Task Hierarchy + +Create a nested Task hierarchy as a child of the current context: + +``` +1. TaskCreate: Sub-feature task β€” subject: "rePACT: {sub-task description}" +2. TaskCreate: Nested phase tasks (as needed): + - "PREPARE: {sub-task-slug}" (if mini-prepare runs) + - "ARCHITECT: {sub-task-slug}" (if mini-architect runs) + - "CODE: {sub-task-slug}" + - "TEST: {sub-task-slug}" (if mini-test runs) +3. TaskUpdate: Set dependencies: + - Phase-to-phase blockedBy chain (same as orchestrate) + - Parent context task addBlockedBy = [sub-feature task] +4. Execute nested Pβ†’Aβ†’Cβ†’T cycle (orchestrator manages all Task state) +5. On completion: + - TaskUpdate: Sub-feature task completed + - Parent task unblocked +``` + +**Skipped phases**: Created and immediately marked `completed` with description noting skip reason. + +**Graceful degradation**: If any Task tool call fails, log a warning and continue. Task integration enhances PACT but should never block it. + +--- + ## Constraints ### Nesting Depth @@ -280,25 +276,39 @@ Consider using /PACT:orchestrate instead. --- +## After Completion + +When nested cycle completes: +1. **Summarize** what was done in the nested cycle +2. **Report** any decisions that affect the parent task +3. **Continue** with parent orchestration + +**Handoff format**: Use the standard 4-item structure (Produced, Key context, Areas of uncertainty, Open questions). See orchestrate.md Β§ Handoff Format. + +The parent orchestration resumes with the sub-task complete. + +--- + ## Signal Monitoring Check TaskList for blocker/algedonic signals: -- After each agent dispatch within nested phases +- After each agent dispatch - When agent reports completion - On any unexpected agent stoppage -On signal detected: Follow Signal Task Handling in CLAUDE.md. +On signal detected: Follow Task Lifecycle Management in CLAUDE.md. --- -## After Completion +## Agent Prompt Language -When nested cycle completes: -1. **TaskUpdate**: Sub-feature task status = "completed" -2. **Summarize** what was done in the nested cycle -3. **Report** any decisions that affect the parent task -4. **Continue** with parent orchestration (parent task now unblocked) +When dispatching agents in nested phases, include this block in the agent prompt: -**Handoff format**: Use the standard 4-item structure (Produced, Key context, Areas of uncertainty, Open questions). See orchestrate.md Β§ Handoff Format. - -The parent orchestration resumes with the sub-task complete. +``` +**Blocker/Signal Protocol**: +- If you hit a blocker, STOP work immediately and report: "BLOCKER: {description}" +- If you detect a viability threat (security, data, ethics), STOP immediately and report: + "⚠️ ALGEDONIC [HALT|ALERT]: {category} β€” {description}" +- Do NOT attempt workarounds for blockers. Do NOT continue work after emitting algedonic signals. +- Always end your response with a structured HANDOFF, even if incomplete. +``` diff --git a/pact-plugin/commands/wrap-up.md b/pact-plugin/commands/wrap-up.md index 994bb86..2b736b6 100644 --- a/pact-plugin/commands/wrap-up.md +++ b/pact-plugin/commands/wrap-up.md @@ -5,37 +5,6 @@ description: Perform end-of-session cleanup and documentation synchronization You are now entering the **Wrap-Up Phase**. Your goal is to ensure the workspace is clean and documentation is synchronized before the session ends or code is committed. -## 0. Task Audit - -Before other cleanup, audit and optionally clean up Task state: - -``` -1. TaskList: Review all session tasks -2. For abandoned in_progress tasks: complete or document reason -3. Verify Feature task reflects final state -4. Archive key context to memory (via pact-memory-agent) -5. Report task summary: "Session has N tasks (X completed, Y pending)" -6. IF multi-session mode (CLAUDE_CODE_TASK_LIST_ID set): - - Offer: "Clean up completed workflows? (Context archived to memory)" - - User confirms β†’ delete completed feature hierarchies - - User declines β†’ leave as-is -``` - -**Cleanup rules** (self-contained for command context): - -| Task State | Cleanup Action | -|------------|----------------| -| `completed` Feature task | Archive summary, then delete with children | -| `in_progress` Feature task | Do NOT delete (workflow still active) | -| Orphaned `in_progress` | Document abandonment reason, then delete | -| `pending` blocked forever | Delete with note | - -**Why conservative:** Tasks are session-scoped by default (fresh on new session). Cleanup only matters for multi-session work, where user explicitly chose persistence via `CLAUDE_CODE_TASK_LIST_ID`. - -> Note: `hooks/stop_audit.py` performs automatic audit checks at session end. This table provides wrap-up command guidance for manual orchestrator-driven cleanup. - ---- - ## 1. Documentation Synchronization - **Scan** the workspace for recent code changes. - **Update** `docs/CHANGELOG.md` with a new entry for this session: @@ -52,9 +21,43 @@ Before other cleanup, audit and optionally clean up Task state: - **Identify** any temporary files created during the session (e.g., `temp_test.py`, `debug.log`, `foo.txt`, `test_output.json`). - **Delete** these files to leave the workspace clean. -## 3. Final Status Report +## 3. Task Audit + +Use Task tools to review and clean up session Tasks: + +``` +1. TaskList: Review all session tasks +2. For abandoned in_progress tasks: + - Determine why they were abandoned + - TaskUpdate: Mark completed with description noting reason + OR document why they remain in_progress +3. Verify Feature task reflects final state: + - All child phases completed or documented + - Metadata reflects actual work done +4. Report task summary: + "Session has N tasks (X completed, Y pending, Z abandoned)" +``` + +### Multi-Session Cleanup + +If `CLAUDE_CODE_TASK_LIST_ID` is set (multi-session mode): +- Offer: "Clean up completed workflows? (Context already archived to memory)" +- If user confirms: Delete completed feature hierarchies to keep Task list manageable +- If user declines: Leave as-is + +**Task list size guidance**: +| Task Count | Action | +|------------|--------| +| < 20 | Normal operation | +| 20-50 | Suggest cleanup | +| > 50 | Warn about performance, strongly suggest cleanup | + +**Graceful degradation**: If TaskList fails, skip Task audit and proceed with other wrap-up activities. + +--- + +## 4. Final Status Report - **Report** a summary of actions taken: - - **Tasks**: N total (X completed, Y pending, Z cleaned up) - Docs updated: [List files] - Files archived: [List files] - Temp files deleted: [List files] diff --git a/pact-plugin/hooks/compaction_refresh.py b/pact-plugin/hooks/compaction_refresh.py index 75a68d3..e373c0d 100644 --- a/pact-plugin/hooks/compaction_refresh.py +++ b/pact-plugin/hooks/compaction_refresh.py @@ -5,132 +5,39 @@ Used by: Claude Code hooks.json SessionStart hook (after session_init.py) This hook fires on SessionStart. It checks if the session was triggered by compaction -(source="compact") and if so, reads workflow state from TaskList (which survives compaction) -to build refresh context. If no TaskList is available, falls back to checkpoint file. - -The Task system (TaskCreate, TaskUpdate, TaskGet, TaskList) is PACT's single source of truth -for workflow state. Tasks persist across compaction at ~/.claude/tasks/{sessionId}/*.json. +(source="compact") and if so, reads the checkpoint file created by precompact_refresh.py. +If an active workflow was in progress, it injects refresh instructions into the session +context to help the orchestrator resume seamlessly. Input: JSON from stdin with: - source: Session start source ("compact" for post-compaction, others for normal start) Output: JSON with hookSpecificOutput.additionalContext (refresh instructions if applicable) -Fallback checkpoint location: ~/.claude/pact-refresh/{encoded-path}.json +Checkpoint location: ~/.claude/pact-refresh/{encoded-path}.json """ import json import os import sys from pathlib import Path -from typing import Any -# Add hooks directory to path for refresh and shared package imports +# Add hooks directory to path for refresh package imports _hooks_dir = Path(__file__).parent if str(_hooks_dir) not in sys.path: sys.path.insert(0, str(_hooks_dir)) # Import checkpoint utilities from refresh package (always available - same directory) -# These are used as fallback when TaskList is unavailable from refresh.checkpoint_builder import ( get_checkpoint_path, get_encoded_project_path, checkpoint_to_refresh_message, ) -# Import shared Task utilities (DRY - used by multiple hooks) -from shared.task_utils import ( - get_task_list, - find_feature_task, - find_current_phase, - find_active_agents, - find_blockers, -) - - -def build_refresh_from_tasks( - feature: dict[str, Any] | None, - phase: dict[str, Any] | None, - agents: list[dict[str, Any]], - blockers: list[dict[str, Any]], -) -> str: - """ - Build refresh context message from Task state. - - Generates a concise message describing the workflow state for - the orchestrator to resume from. - - Args: - feature: Feature task dict or None - phase: Current phase task dict or None - agents: List of active agent tasks - blockers: List of active blocker tasks - - Returns: - Formatted refresh message string - """ - lines = ["[POST-COMPACTION CHECKPOINT]"] - lines.append("Prior conversation auto-compacted. Resume from Task state below:") - - # Feature context - if feature: - feature_subject = feature.get("subject", "unknown feature") - feature_id = feature.get("id", "") - if feature_id: - lines.append(f"Feature: {feature_subject} (id: {feature_id})") - else: - lines.append(f"Feature: {feature_subject}") - else: - lines.append("Feature: Unable to identify feature task") - - # Phase context - if phase: - phase_subject = phase.get("subject", "unknown phase") - lines.append(f"Current Phase: {phase_subject}") - else: - lines.append("Current Phase: None detected") - - # Active agents - if agents: - agent_names = [a.get("subject", "unknown") for a in agents] - lines.append(f"Active Agents ({len(agents)}): {', '.join(agent_names)}") - else: - lines.append("Active Agents: None") - - # Blockers (critical info) - if blockers: - lines.append("") - lines.append("**BLOCKERS DETECTED:**") - for blocker in blockers: - subj = blocker.get("subject", "unknown blocker") - meta = blocker.get("metadata", {}) - level = meta.get("level", "") - if level: - lines.append(f" - {level}: {subj}") - else: - lines.append(f" - {subj}") - - # Next step guidance - lines.append("") - if blockers: - lines.append("Next Step: **Address blockers before proceeding.**") - elif agents: - lines.append("Next Step: Monitor active agents via TaskList, then proceed.") - elif phase: - lines.append("Next Step: Continue current phase or check agent completion.") - else: - lines.append("Next Step: **Check TaskList and ask user how to proceed.**") - - return "\n".join(lines) - - -# ----------------------------------------------------------------------------- -# Checkpoint Fallback (Legacy State Source) -# ----------------------------------------------------------------------------- def read_checkpoint(checkpoint_path: Path) -> dict | None: """ - Read and parse the checkpoint file (fallback when Tasks unavailable). + Read and parse the checkpoint file. Args: checkpoint_path: Path to the checkpoint file @@ -183,9 +90,9 @@ def validate_checkpoint(checkpoint: dict, current_session_id: str) -> bool: return True -def build_refresh_message_from_checkpoint(checkpoint: dict) -> str: +def build_refresh_message(checkpoint: dict) -> str: """ - Build the refresh instruction message from checkpoint (fallback). + Build the refresh instruction message for the orchestrator. Delegates to checkpoint_to_refresh_message from the refresh package. @@ -198,22 +105,10 @@ def build_refresh_message_from_checkpoint(checkpoint: dict) -> str: return checkpoint_to_refresh_message(checkpoint) -# Alias for backward compatibility with tests -build_refresh_message = build_refresh_message_from_checkpoint - - -# ----------------------------------------------------------------------------- -# Main Entry Point -# ----------------------------------------------------------------------------- - def main(): """ Main entry point for the SessionStart refresh hook. - Strategy: - 1. Primary: Read TaskList directly (Tasks survive compaction) - 2. Fallback: Read checkpoint file if TaskList unavailable - Checks if this is a post-compaction session and injects refresh instructions if an active workflow was in progress. """ @@ -231,45 +126,9 @@ def main(): # Not a post-compaction session, no action needed sys.exit(0) + # Get session ID and project path + # Use empty transcript path to trigger fallback to CLAUDE_PROJECT_DIR session_id = os.environ.get("CLAUDE_SESSION_ID", "unknown") - - # --------------------------------------------------------------------- - # Primary: Try TaskList first (Tasks survive compaction) - # --------------------------------------------------------------------- - tasks = get_task_list() - - if tasks: - # Find workflow state from Tasks - in_progress = [t for t in tasks if t.get("status") == "in_progress"] - - if in_progress: - feature_task = find_feature_task(tasks) - current_phase = find_current_phase(tasks) - active_agents = find_active_agents(tasks) - blockers = find_blockers(tasks) - - refresh_message = build_refresh_from_tasks( - feature=feature_task, - phase=current_phase, - agents=active_agents, - blockers=blockers, - ) - - output = { - "hookSpecificOutput": { - "hookEventName": "SessionStart", - "additionalContext": refresh_message - } - } - print(json.dumps(output)) - sys.exit(0) - - # Tasks exist but nothing in_progress - no active workflow - sys.exit(0) - - # --------------------------------------------------------------------- - # Fallback: Read checkpoint file (legacy approach) - # --------------------------------------------------------------------- encoded_path = get_encoded_project_path("") if encoded_path == "unknown-project": @@ -307,8 +166,8 @@ def main(): # No active workflow at compaction time sys.exit(0) - # Build and inject refresh instructions from checkpoint - refresh_message = build_refresh_message_from_checkpoint(checkpoint) + # Build and inject refresh instructions + refresh_message = build_refresh_message(checkpoint) output = { "hookSpecificOutput": { diff --git a/pact-plugin/hooks/phase_completion.py b/pact-plugin/hooks/phase_completion.py index eb7efb6..ba5ba6b 100755 --- a/pact-plugin/hooks/phase_completion.py +++ b/pact-plugin/hooks/phase_completion.py @@ -4,11 +4,8 @@ Summary: Stop hook that verifies phase completion and reminds about decision logs. Used by: Claude Code settings.json Stop hook -Checks for CODE phase completion without decision logs and reminds about -documentation and testing requirements. - -With Task integration, phase completion is detected via Task statuses first, -falling back to transcript parsing if TaskList unavailable. +Checks for CODE phase completion without decision logs and +reminds about documentation requirements. Input: JSON from stdin with session transcript/context Output: JSON with `systemMessage` for reminders if needed @@ -18,18 +15,9 @@ import sys import os from pathlib import Path -from typing import Any - -# Add hooks directory to path for shared package imports -_hooks_dir = Path(__file__).parent -if str(_hooks_dir) not in sys.path: - sys.path.insert(0, str(_hooks_dir)) - -# Import shared Task utilities (DRY - used by multiple hooks) -from shared.task_utils import get_task_list -# Indicators that CODE phase work was performed (for transcript fallback) +# Indicators that CODE phase work was performed CODE_PHASE_INDICATORS = [ "pact-backend-coder", "pact-frontend-coder", @@ -50,75 +38,9 @@ ] -def check_phase_completion_via_tasks(tasks: list[dict[str, Any]]) -> dict[str, Any]: - """ - Check phase completion status using Task system. - - Analyzes Task statuses to determine: - - If CODE phase is completed - - If TEST phase has started - - Any phase completion reminders needed - - Args: - tasks: List of all tasks - - Returns: - Dict with: - - code_completed: bool - - test_started: bool - - test_completed: bool - - reminders: list of reminder messages - """ - result = { - "code_completed": False, - "test_started": False, - "test_completed": False, - "reminders": [], - } - - code_phase = None - test_phase = None - - for task in tasks: - subject = task.get("subject", "") - status = task.get("status", "") - - if subject.startswith("CODE:"): - code_phase = task - if status == "completed": - result["code_completed"] = True - - if subject.startswith("TEST:"): - test_phase = task - if status == "in_progress": - result["test_started"] = True - elif status == "completed": - result["test_started"] = True - result["test_completed"] = True - - # Generate reminders based on Task state - if result["code_completed"] and not result["test_started"]: - result["reminders"].append( - "TEST Phase Reminder: CODE phase completed. Consider invoking " - "pact-test-engineer to verify the implementation." - ) - - if test_phase and test_phase.get("status") == "pending": - result["reminders"].append( - "TEST Phase Reminder: TEST phase is pending (blocked). " - "Check blockedBy to see what needs to complete first." - ) - - return result - - -# ----------------------------------------------------------------------------- -# Transcript Fallback (Legacy Detection) -# ----------------------------------------------------------------------------- - def check_for_code_phase_activity(transcript: str) -> bool: """ - Determine if CODE phase agents were invoked in this session (fallback). + Determine if CODE phase agents were invoked in this session. Args: transcript: The session transcript @@ -188,11 +110,8 @@ def main(): """ Main entry point for the Stop hook. - Strategy: - 1. Primary: Check Task statuses for phase completion (Task integration) - 2. Fallback: Check transcript for CODE phase indicators - - Reminds about decision logs and testing if appropriate. + Checks for CODE phase indicators and reminds about decision logs + and testing if not mentioned in the session. """ try: # Read input from stdin @@ -204,44 +123,18 @@ def main(): project_dir = os.environ.get("CLAUDE_PROJECT_DIR", ".") transcript = input_data.get("transcript", "") + # If no transcript, nothing to check + if not transcript: + sys.exit(0) + messages = [] - was_code_phase = False - - # --------------------------------------------------------------------- - # Primary: Check Task statuses (Task integration) - # --------------------------------------------------------------------- - tasks = get_task_list() - - if tasks: - phase_status = check_phase_completion_via_tasks(tasks) - - # Add any Task-derived reminders - messages.extend(phase_status.get("reminders", [])) - - # Track CODE phase completion for decision log check - was_code_phase = phase_status.get("code_completed", False) - - # --------------------------------------------------------------------- - # Fallback: Check transcript if no Task state - # --------------------------------------------------------------------- - elif transcript: - was_code_phase = check_for_code_phase_activity(transcript) - - if was_code_phase: - # Check if testing was addressed (transcript-based) - testing_discussed = check_for_test_reminders(transcript) - if not testing_discussed: - messages.append( - "TEST Phase Reminder: Consider invoking pact-test-engineer " - "to verify the implementation." - ) - - # --------------------------------------------------------------------- - # Common checks (regardless of source) - # --------------------------------------------------------------------- + + # Check for CODE phase activity + was_code_phase = check_for_code_phase_activity(transcript) + if was_code_phase: # Check if decision logs were addressed - decision_log_mentioned = transcript and check_decision_log_mentioned(transcript) + decision_log_mentioned = check_decision_log_mentioned(transcript) decision_logs_exist = check_decision_logs_exist(project_dir) if not decision_log_mentioned and not decision_logs_exist: @@ -251,6 +144,14 @@ def main(): "implementation decisions and trade-offs." ) + # Check if testing was addressed + testing_discussed = check_for_test_reminders(transcript) + if not testing_discussed: + messages.append( + "TEST Phase Reminder: Consider invoking pact-test-engineer " + "to verify the implementation." + ) + # Output messages if any reminders are needed if messages: output = { diff --git a/pact-plugin/hooks/precompact_refresh.py b/pact-plugin/hooks/precompact_refresh.py index 3528a37..52e33c0 100644 --- a/pact-plugin/hooks/precompact_refresh.py +++ b/pact-plugin/hooks/precompact_refresh.py @@ -4,18 +4,10 @@ Summary: PreCompact hook that extracts workflow state from transcript before compaction. Used by: Claude Code hooks.json PreCompact hook -DEPRECATION NOTE (Task System Integration): -With PACT Task integration, Tasks persist across compaction at ~/.claude/tasks/{sessionId}/. -This hook becomes largely redundant since compaction_refresh.py can read TaskList directly. -Kept for backward compatibility and edge cases where Task system is unavailable. - -Consider removing this hook once Task integration is fully rolled out and stable. -The checkpoint file serves as a fallback when TaskList reading fails. - This hook fires just before context compaction occurs. It parses the conversation transcript to extract the current workflow state (if any) and writes a checkpoint file. The checkpoint is then read by compaction_refresh.py on SessionStart to -inject refresh instructions into the resumed session (as fallback to TaskList). +inject refresh instructions into the resumed session. Input: JSON from stdin with: - transcript_path: Path to the JSONL conversation transcript diff --git a/pact-plugin/hooks/session_init.py b/pact-plugin/hooks/session_init.py index 0cb8fd4..306c193 100755 --- a/pact-plugin/hooks/session_init.py +++ b/pact-plugin/hooks/session_init.py @@ -9,7 +9,6 @@ 2. Detects active plans and notifies user 3. Updates ~/.claude/CLAUDE.md (merges/installs PACT Orchestrator) 4. Ensures project CLAUDE.md exists with memory sections -5. Checks for in_progress Tasks (resumption context via Task integration) Note: Memory-related initialization (dependency installation, embedding migration, pending embedding catch-up) is now lazy-loaded on first memory @@ -24,15 +23,6 @@ import sys import os from pathlib import Path -from typing import Any - -# Add hooks directory to path for shared package imports -_hooks_dir = Path(__file__).parent -if str(_hooks_dir) not in sys.path: - sys.path.insert(0, str(_hooks_dir)) - -# Import shared Task utilities (DRY - used by multiple hooks) -from shared.task_utils import get_task_list def setup_plugin_symlinks() -> str | None: @@ -276,74 +266,6 @@ def ensure_project_memory_md() -> str | None: return f"Project CLAUDE.md failed: {str(e)[:30]}" -def check_resumption_context(tasks: list[dict[str, Any]]) -> str | None: - """ - Check if there are in_progress Tasks indicating work to resume. - - This helps users understand the current state when starting a new session - with a persistent task list (CLAUDE_CODE_TASK_LIST_ID set). - - Args: - tasks: List of all tasks - - Returns: - Status message describing resumption context, or None if nothing to report - """ - in_progress = [t for t in tasks if t.get("status") == "in_progress"] - pending = [t for t in tasks if t.get("status") == "pending"] - completed = [t for t in tasks if t.get("status") == "completed"] - - if not in_progress and not pending: - return None - - # Count by type - feature_tasks = [] - phase_tasks = [] - agent_tasks = [] - blocker_tasks = [] - - for task in in_progress: - subject = task.get("subject", "") - metadata = task.get("metadata", {}) - - if metadata.get("type") in ("blocker", "algedonic"): - blocker_tasks.append(task) - elif any(subject.startswith(p) for p in ("PREPARE:", "ARCHITECT:", "CODE:", "TEST:")): - phase_tasks.append(task) - elif any(subject.lower().startswith(p) for p in ("pact-",)): - agent_tasks.append(task) - else: - # Assume it's a feature task - feature_tasks.append(task) - - parts = [] - - if feature_tasks: - names = [t.get("subject", "unknown")[:30] for t in feature_tasks[:2]] - if len(feature_tasks) > 2: - parts.append(f"Features: {', '.join(names)} (+{len(feature_tasks)-2} more)") - else: - parts.append(f"Features: {', '.join(names)}") - - if phase_tasks: - phases = [t.get("subject", "").split(":")[0] for t in phase_tasks] - parts.append(f"Phases: {', '.join(phases)}") - - if agent_tasks: - parts.append(f"Active agents: {len(agent_tasks)}") - - if blocker_tasks: - parts.append(f"**Blockers: {len(blocker_tasks)}**") - - if parts: - summary = f"Resumption context: {' | '.join(parts)}" - if pending: - summary += f" ({len(pending)} pending)" - return summary - - return None - - def main(): """ Main entry point for the SessionStart hook. @@ -353,7 +275,6 @@ def main(): 2. Checks for active plans 3. Updates ~/.claude/CLAUDE.md (merges/installs PACT Orchestrator) 4. Ensures project CLAUDE.md exists with memory sections - 5. Checks for in_progress Tasks (resumption context via Task integration) Memory initialization (dependencies, migrations, embedding catch-up) is now lazy-loaded on first memory operation to reduce startup cost for @@ -400,17 +321,6 @@ def main(): else: context_parts.append(project_md_msg) - # 5. Check for in_progress Tasks (resumption context via Task integration) - tasks = get_task_list() - if tasks: - resumption_msg = check_resumption_context(tasks) - if resumption_msg: - # Blockers are critical - put in system message for visibility - if "**Blockers:" in resumption_msg: - system_messages.append(resumption_msg) - else: - context_parts.append(resumption_msg) - # Build output output = {} diff --git a/pact-plugin/hooks/shared/__init__.py b/pact-plugin/hooks/shared/__init__.py deleted file mode 100644 index ac5fcc7..0000000 --- a/pact-plugin/hooks/shared/__init__.py +++ /dev/null @@ -1,24 +0,0 @@ -""" -Location: pact-plugin/hooks/shared/__init__.py -Summary: Package for shared hook utilities. -Used by: Various PACT hooks that need common Task system integration. - -This package provides shared utilities for hooks, primarily Task system -integration functions that are used across multiple hooks. -""" - -from .task_utils import ( - get_task_list, - find_feature_task, - find_current_phase, - find_active_agents, - find_blockers, -) - -__all__ = [ - "get_task_list", - "find_feature_task", - "find_current_phase", - "find_active_agents", - "find_blockers", -] diff --git a/pact-plugin/hooks/shared/task_utils.py b/pact-plugin/hooks/shared/task_utils.py deleted file mode 100644 index e8be4a4..0000000 --- a/pact-plugin/hooks/shared/task_utils.py +++ /dev/null @@ -1,190 +0,0 @@ -""" -Location: pact-plugin/hooks/shared/task_utils.py -Summary: Shared Task system integration utilities for PACT hooks. -Used by: compaction_refresh.py, validate_handoff.py, phase_completion.py, - session_init.py, stop_audit.py - -This module provides common functions for reading and analyzing Tasks from -the Claude Task system. Tasks are stored at ~/.claude/tasks/{sessionId}/*.json -and survive context compaction, making them the primary state source for -workflow recovery. - -Functions: - get_task_list: Read all tasks from the Task system - find_feature_task: Identify the main Feature task - find_current_phase: Find the currently active phase task - find_active_agents: Find all active agent tasks - find_blockers: Find blocker/algedonic tasks -""" - -import json -import os -from pathlib import Path -from typing import Any - - -def get_task_list() -> list[dict[str, Any]] | None: - """ - Read TaskList from the Claude Task system. - - Tasks are stored at ~/.claude/tasks/{sessionId}/*.json and survive compaction. - This function reads directly from the filesystem since hooks cannot call Task tools. - - Returns: - List of task dicts, or None if tasks unavailable - """ - session_id = os.environ.get("CLAUDE_SESSION_ID", "") - # Also check for multi-session task list ID - task_list_id = os.environ.get("CLAUDE_CODE_TASK_LIST_ID", session_id) - - if not task_list_id: - return None - - tasks_dir = Path.home() / ".claude" / "tasks" / task_list_id - if not tasks_dir.exists(): - return None - - tasks = [] - try: - for task_file in tasks_dir.glob("*.json"): - try: - content = task_file.read_text(encoding='utf-8') - task = json.loads(content) - tasks.append(task) - except (IOError, json.JSONDecodeError): - continue - except Exception: - return None - - return tasks if tasks else None - - -def find_feature_task(tasks: list[dict[str, Any]]) -> dict[str, Any] | None: - """ - Find the main Feature task from the task list. - - Feature tasks are top-level tasks that represent the overall work item. - They can be identified by: - - Having no blockedBy (top-level) - - Subject starting with a verb (e.g., "Implement user auth") - - OR having phase tasks as children - - Args: - tasks: List of all tasks - - Returns: - Feature task dict, or None if not found - """ - # Look for tasks with no blockedBy that have children - task_ids = {t.get("id") for t in tasks if t.get("id")} - blocked_by_ids = set() - for task in tasks: - blocked_by = task.get("blockedBy", []) - if blocked_by: - blocked_by_ids.update(blocked_by) - - # Feature task is one that blocks others but isn't blocked itself - # (or has status in_progress at top level) - for task in tasks: - task_id = task.get("id") - if not task_id: - continue - - # Skip if this task is blocked by something - if task.get("blockedBy"): - continue - - # Check if it's a feature-like task (not a phase task) - subject = task.get("subject", "") - # Phase tasks start with phase names - phase_prefixes = ("PREPARE:", "ARCHITECT:", "CODE:", "TEST:", "Review:") - if any(subject.startswith(p) for p in phase_prefixes): - continue - - # This looks like a feature task - if task.get("status") in ("in_progress", "pending"): - return task - - return None - - -def find_current_phase(tasks: list[dict[str, Any]]) -> dict[str, Any] | None: - """ - Find the currently active phase task. - - Phase tasks follow the pattern: "{PHASE}: {feature-slug}" - The current phase is the one with status "in_progress". - - Args: - tasks: List of all tasks - - Returns: - Phase task dict, or None if not found - """ - phase_prefixes = ("PREPARE:", "ARCHITECT:", "CODE:", "TEST:") - - for task in tasks: - subject = task.get("subject", "") - if any(subject.startswith(p) for p in phase_prefixes): - if task.get("status") == "in_progress": - return task - - return None - - -def find_active_agents(tasks: list[dict[str, Any]]) -> list[dict[str, Any]]: - """ - Find all currently active agent tasks. - - Agent tasks follow the pattern: "{agent-type}: {work-description}" - and have status "in_progress". - - Args: - tasks: List of all tasks - - Returns: - List of agent task dicts - """ - agent_prefixes = ( - "pact-preparer:", - "pact-architect:", - "pact-backend-coder:", - "pact-frontend-coder:", - "pact-database-engineer:", - "pact-test-engineer:", - "pact-memory-agent:", - "pact-n8n:", - ) - - active = [] - for task in tasks: - subject = task.get("subject", "").lower() - if any(subject.startswith(p) for p in agent_prefixes): - if task.get("status") == "in_progress": - active.append(task) - - return active - - -def find_blockers(tasks: list[dict[str, Any]]) -> list[dict[str, Any]]: - """ - Find any blocker or algedonic tasks. - - These are signal tasks created by agents when they hit blockers - or detect viability threats. - - Args: - tasks: List of all tasks - - Returns: - List of blocker/algedonic task dicts - """ - blockers = [] - for task in tasks: - metadata = task.get("metadata", {}) - task_type = metadata.get("type", "") - if task_type in ("blocker", "algedonic"): - if task.get("status") != "completed": - blockers.append(task) - - return blockers diff --git a/pact-plugin/hooks/stop_audit.py b/pact-plugin/hooks/stop_audit.py deleted file mode 100755 index 8ddb81e..0000000 --- a/pact-plugin/hooks/stop_audit.py +++ /dev/null @@ -1,200 +0,0 @@ -#!/usr/bin/env python3 -""" -Location: pact-plugin/hooks/stop_audit.py -Summary: Stop hook that audits session state including Tasks and uncommitted changes. -Used by: Claude Code settings.json Stop hook - -Audits for: -1. Orphaned in_progress Tasks (workflow may be incomplete) -2. Uncommitted file changes (git working tree) - -This replaces the older stop_audit.sh shell script with Task system integration. - -Input: JSON from stdin with session context -Output: JSON with `systemMessage` for audit warnings if needed -""" - -import json -import os -import subprocess -import sys -from pathlib import Path -from typing import Any - -# Add hooks directory to path for shared package imports -_hooks_dir = Path(__file__).parent -if str(_hooks_dir) not in sys.path: - sys.path.insert(0, str(_hooks_dir)) - -# Import shared Task utilities (DRY - used by multiple hooks) -from shared.task_utils import get_task_list - - -def audit_tasks(tasks: list[dict[str, Any]]) -> list[str]: - """ - Audit Tasks for incomplete workflows. - - Checks for: - - Orphaned in_progress Tasks (agents that didn't complete) - - Unresolved blocker/algedonic Tasks - - Args: - tasks: List of all tasks - - Returns: - List of warning messages - """ - warnings = [] - - in_progress = [t for t in tasks if t.get("status") == "in_progress"] - pending_blockers = [] - orphaned_agents = [] - - for task in in_progress: - subject = task.get("subject", "") - metadata = task.get("metadata", {}) - task_type = metadata.get("type", "") - - if task_type in ("blocker", "algedonic"): - pending_blockers.append(task) - elif any(subject.lower().startswith(p) for p in ( - "pact-preparer:", - "pact-architect:", - "pact-backend-coder:", - "pact-frontend-coder:", - "pact-database-engineer:", - "pact-test-engineer:", - "pact-memory-agent:", - )): - orphaned_agents.append(task) - - if pending_blockers: - blocker_subjects = [t.get("subject", "unknown")[:40] for t in pending_blockers] - warnings.append( - f"Unresolved blockers ({len(pending_blockers)}): " - f"{', '.join(blocker_subjects[:3])}" - + (f" (+{len(blocker_subjects)-3} more)" if len(blocker_subjects) > 3 else "") - ) - - if orphaned_agents: - agent_subjects = [t.get("subject", "").split(":")[0] for t in orphaned_agents] - warnings.append( - f"Agents still in_progress ({len(orphaned_agents)}): " - f"{', '.join(agent_subjects[:3])}" - + (f" (+{len(agent_subjects)-3} more)" if len(agent_subjects) > 3 else "") - ) - - # Summary stats - completed = len([t for t in tasks if t.get("status") == "completed"]) - pending = len([t for t in tasks if t.get("status") == "pending"]) - - if in_progress or pending: - warnings.append( - f"Task summary: {completed} completed, {len(in_progress)} in_progress, " - f"{pending} pending" - ) - - return warnings - - -def audit_git_changes() -> list[str]: - """ - Audit for uncommitted git changes. - - Returns: - List of warning messages about uncommitted files - """ - warnings = [] - - try: - result = subprocess.run( - ["git", "status", "--porcelain"], - capture_output=True, - text=True, - timeout=5, - ) - - if result.returncode != 0: - return [] # Not in a git repo or git error - - changes = result.stdout.strip() - if not changes: - return [] - - # Count changes by type - lines = changes.split("\n") - modified = [l for l in lines if l.startswith(" M") or l.startswith("M ")] - added = [l for l in lines if l.startswith("A ") or l.startswith("??")] - deleted = [l for l in lines if l.startswith(" D") or l.startswith("D ")] - - parts = [] - if modified: - parts.append(f"{len(modified)} modified") - if added: - parts.append(f"{len(added)} added/untracked") - if deleted: - parts.append(f"{len(deleted)} deleted") - - if parts: - warnings.append(f"Uncommitted changes: {', '.join(parts)}") - - # List first few files - files = [l[3:].strip() for l in lines[:5]] - if files: - file_list = ", ".join(files) - if len(lines) > 5: - file_list += f" (+{len(lines)-5} more)" - warnings.append(f"Files: {file_list}") - - except subprocess.TimeoutExpired: - pass - except FileNotFoundError: - pass # git not installed - except Exception: - pass - - return warnings - - -def main(): - """ - Main entry point for the Stop audit hook. - - Audits session state and warns about incomplete workflows or uncommitted changes. - """ - try: - # Read input from stdin (may contain transcript or other context) - try: - input_data = json.load(sys.stdin) - except json.JSONDecodeError: - input_data = {} - - warnings = [] - - # Audit Tasks for incomplete workflows - tasks = get_task_list() - if tasks: - task_warnings = audit_tasks(tasks) - warnings.extend(task_warnings) - - # Audit git for uncommitted changes - git_warnings = audit_git_changes() - warnings.extend(git_warnings) - - # Output warnings if any - if warnings: - output = { - "systemMessage": "=== PACT Session Audit ===\n" + "\n".join(warnings) - } - print(json.dumps(output)) - - sys.exit(0) - - except Exception as e: - # Don't block on errors - just warn - print(f"Hook warning (stop_audit): {e}", file=sys.stderr) - sys.exit(0) - - -if __name__ == "__main__": - main() diff --git a/pact-plugin/hooks/validate_handoff.py b/pact-plugin/hooks/validate_handoff.py index 59945f1..5f329c8 100755 --- a/pact-plugin/hooks/validate_handoff.py +++ b/pact-plugin/hooks/validate_handoff.py @@ -1,23 +1,19 @@ #!/usr/bin/env python3 """ Location: pact-plugin/hooks/validate_handoff.py -Summary: SubagentStop hook that validates PACT agent handoff format and Task protocol. +Summary: SubagentStop hook that validates PACT agent handoff format. Used by: Claude Code settings.json SubagentStop hook -Validates that PACT agents: -1. Complete with proper handoff information (produced, decisions, next steps) -2. Called TaskUpdate(status="completed") with required metadata (Task integration) +Checks that PACT agents complete with proper handoff information +containing: what was produced, key decisions, next agent needs. -Input: JSON from stdin with `transcript`, `agent_id`, and optionally `task_id` -Output: JSON with `systemMessage` if handoff format is incomplete or Task protocol violated +Input: JSON from stdin with `transcript` and `agent_id` +Output: JSON with `systemMessage` if handoff format is incomplete """ import json -import os import sys import re -from pathlib import Path -from typing import Any # Required handoff elements with their patterns and descriptions @@ -48,9 +44,6 @@ }, } -# Required metadata fields for Task completion -REQUIRED_TASK_METADATA = ["produced", "decisions"] - def validate_handoff(transcript: str) -> tuple: """ @@ -112,110 +105,12 @@ def is_pact_agent(agent_id: str) -> bool: return any(agent_id.startswith(prefix) for prefix in pact_prefixes) -# ----------------------------------------------------------------------------- -# Task Protocol Validation (Task System Integration) -# ----------------------------------------------------------------------------- - -def get_task_by_id(task_id: str) -> dict[str, Any] | None: - """ - Read a specific task from the Task system by ID. - - Tasks are stored at ~/.claude/tasks/{sessionId}/{taskId}.json. - - Args: - task_id: The task ID to look up - - Returns: - Task dict, or None if not found - """ - session_id = os.environ.get("CLAUDE_SESSION_ID", "") - task_list_id = os.environ.get("CLAUDE_CODE_TASK_LIST_ID", session_id) - - if not task_list_id or not task_id: - return None - - # Try to find the task file - tasks_dir = Path.home() / ".claude" / "tasks" / task_list_id - - # Task files might be named by ID or contain the ID in metadata - # Try direct lookup first - task_file = tasks_dir / f"{task_id}.json" - if task_file.exists(): - try: - content = task_file.read_text(encoding='utf-8') - return json.loads(content) - except (IOError, json.JSONDecodeError): - pass - - # Fall back to scanning all tasks - if tasks_dir.exists(): - try: - for f in tasks_dir.glob("*.json"): - try: - content = f.read_text(encoding='utf-8') - task = json.loads(content) - if task.get("id") == task_id: - return task - except (IOError, json.JSONDecodeError): - continue - except Exception: - pass - - return None - - -def validate_task_completion(agent_id: str, task_id: str) -> tuple[bool, list[str]]: - """ - Validate that the agent properly completed its Task. - - Checks: - 1. Task status was updated to "completed" - 2. Task metadata contains required handoff fields (produced, decisions) - - Args: - agent_id: The agent identifier - task_id: The task ID assigned to the agent - - Returns: - Tuple of (is_valid, list of warnings) - """ - warnings = [] - - task = get_task_by_id(task_id) - if task is None: - # Can't find task - might be Task system unavailable or ID invalid - # Don't warn harshly, just note it - return True, [] # Assume valid if we can't check - - # Check status was updated - status = task.get("status", "") - if status != "completed": - warnings.append( - f"Agent '{agent_id}' did not mark Task {task_id} as completed (status: {status})" - ) - - # Check metadata contains required handoff fields - metadata = task.get("metadata") or {} - missing_fields = [f for f in REQUIRED_TASK_METADATA if f not in metadata] - - if missing_fields: - warnings.append( - f"Agent '{agent_id}' Task metadata missing: {', '.join(missing_fields)}" - ) - - is_valid = len(warnings) == 0 - return is_valid, warnings - - def main(): """ Main entry point for the SubagentStop hook. - Reads agent transcript from stdin, validates both: - 1. Handoff format (prose) for PACT agents - 2. Task protocol compliance (if task_id provided) - - Outputs warning messages if validation fails. + Reads agent transcript from stdin, validates handoff format for PACT agents, + and outputs a warning message if the handoff is incomplete. """ try: # Read input from stdin @@ -227,34 +122,25 @@ def main(): transcript = input_data.get("transcript", "") agent_id = input_data.get("agent_id", "") - task_id = input_data.get("task_id", "") # Only validate PACT agents if not is_pact_agent(agent_id): sys.exit(0) - warnings = [] + # Skip validation if transcript is very short (likely an error case) + if len(transcript) < 100: + sys.exit(0) - # Skip transcript validation if very short (likely an error case) - if len(transcript) >= 100: - is_valid, missing = validate_handoff(transcript) + is_valid, missing = validate_handoff(transcript) - if not is_valid and missing: - warnings.append( + if not is_valid and missing: + output = { + "systemMessage": ( f"PACT Handoff Warning: Agent '{agent_id}' completed without " f"proper handoff. Missing: {', '.join(missing)}. " - "Consider including: what was produced, key decisions, and next steps." + "Consider including: what was produced, key decisions, and next steps. " + "See pact-protocols.md for handoff format." ) - - # Validate Task protocol if task_id was provided - if task_id: - task_valid, task_warnings = validate_task_completion(agent_id, task_id) - warnings.extend(task_warnings) - - # Output warnings if any - if warnings: - output = { - "systemMessage": " | ".join(warnings) } print(json.dumps(output)) diff --git a/pact-plugin/protocols/algedonic.md b/pact-plugin/protocols/algedonic.md index fa54107..a0ea8e5 100644 --- a/pact-plugin/protocols/algedonic.md +++ b/pact-plugin/protocols/algedonic.md @@ -81,52 +81,32 @@ Apply the S5 Decision Framing Protocol (see [pact-s5-policy.md](pact-s5-policy.m **Protocol-level, not architectural**: Agents emit algedonic signals through the same communication channel (to orchestrator), but the SIGNAL FORMAT itself demands immediate user escalation. The orchestrator is **REQUIRED** to surface algedonic signals to user immediatelyβ€”it cannot triage, delay, or suppress them. -### Task System Integration - -With PACT Task integration, algedonic signals create **signal Tasks** that persist and block affected work: - -**Agent behavior on algedonic signal:** -``` -1. TaskCreate(subject="[HALT|ALERT]: {category}", metadata={"type": "algedonic", "level": "HALT|ALERT", "category": "..."}) -2. TaskUpdate(own_task_id, addBlockedBy=[algedonic_task_id]) -3. Stop work immediately -4. Report signal to orchestrator -``` - -**Orchestrator amplifies scope:** -| Signal Level | Orchestrator Action | -|--------------|---------------------| -| **ALERT** | `TaskUpdate(current_phase_id, addBlockedBy=[algedonic_id])` β€” blocks phase | -| **HALT** | `TaskUpdate(feature_task_id, addBlockedBy=[algedonic_id])` β€” blocks entire workflow | - -**Resolution:** -``` -1. User acknowledges/resolves the issue -2. TaskUpdate(algedonic_task_id, status="completed") -3. Blocked Tasks become unblocked automatically -4. Work can resume -``` - -This makes algedonic signals **visible in TaskList** and ensures they properly block affected work until resolved. - ### Flow ``` Agent detects trigger condition ↓ -Agent creates algedonic Task + blocks self (addBlockedBy) +Agent STOPS immediately β€” no further work ↓ -Agent emits algedonic signal (HALT or ALERT) to orchestrator +Agent reports via text: "⚠️ ALGEDONIC [HALT|ALERT]: {category} β€” {description}" ↓ -Orchestrator amplifies scope (blocks phase or feature Task) +Agent ends with partial HANDOFF + ↓ +Orchestrator receives agent response + ↓ +Orchestrator creates algedonic Task (TaskCreate) + ↓ +Orchestrator amplifies scope: + - ALERT: blocks current phase Task + - HALT: blocks feature Task (entire workflow) ↓ Orchestrator IMMEDIATELY presents to user (no other work continues) ↓ User responds ↓ -Algedonic Task marked completed +Orchestrator marks algedonic Task completed (TaskUpdate) ↓ -Work resumes (or stops) based on user decision +Blocked Tasks can proceed; work resumes (or stops) based on user decision ``` ### Orchestrator Behavior @@ -178,6 +158,30 @@ User must provide **explicit justification** that: - "Continue with caution" β€” Proceed but with awareness - "Stop work" β€” Halt all activity +### Task System Integration + +Agents do NOT have access to Task tools. They report algedonic signals via structured text. The orchestrator translates agent text into Task state changes. + +**Agent behavior on algedonic signal:** +1. STOP immediately β€” no further work +2. Report: `⚠️ ALGEDONIC [HALT|ALERT]: {category} β€” {description}` +3. End with partial HANDOFF (even incomplete work should be reported) + +**Orchestrator behavior on receiving algedonic text:** +1. Create algedonic Task: `TaskCreate(subject="⚠️ [HALT|ALERT]: {category}", metadata={"type": "algedonic", "level": "...", "category": "..."})` +2. Amplify scope: + - **ALERT**: `TaskUpdate(current_phase_id, addBlockedBy=[algedonic_id])` β€” blocks phase + - **HALT**: `TaskUpdate(feature_task_id, addBlockedBy=[algedonic_id])` β€” blocks entire workflow +3. Surface to user immediately + +**Resolution:** +1. User acknowledges/resolves the issue +2. `TaskUpdate(algedonic_task_id, status="completed")` +3. Blocked Tasks become unblocked automatically +4. Work can resume + +This makes algedonic signals **visible in TaskList** and ensures they properly block affected work until resolved. + --- ## Who Can Emit Algedonic Signals diff --git a/pact-plugin/protocols/pact-protocols.md b/pact-plugin/protocols/pact-protocols.md index 9542851..817de19 100644 --- a/pact-plugin/protocols/pact-protocols.md +++ b/pact-plugin/protocols/pact-protocols.md @@ -366,26 +366,6 @@ When a checkpoint surfaces tension, apply the Resolution Protocol above. The coordination layer enables parallel agent operation without conflicts. S2 is **proactive** (prevents conflicts) not just **reactive** (resolves conflicts). Apply these protocols whenever multiple agents work concurrently. -### Task System Integration - -With PACT Task integration, the TaskList serves as a **shared state mechanism** for coordination: - -| Use Case | How TaskList Helps | -|----------|-------------------| -| **Conflict detection** | Query TaskList to see what files/components other agents are working on | -| **Parallel agent visibility** | All in_progress agent Tasks visible via TaskList | -| **Convention propagation** | First agent's metadata (decisions, patterns) queryable by later agents | -| **Resource claims** | Agent Tasks can include metadata about claimed resources | - -**Coordination via Tasks:** -``` -Before parallel dispatch: -1. TaskList β†’ check for in_progress agents on same files -2. If conflict detected β†’ sequence or assign boundaries -3. Dispatch agents with Task IDs -4. Monitor via TaskList for completion/blockers -``` - ### Information Flows S2 manages information flow between agents: @@ -395,7 +375,6 @@ S2 manages information flow between agents: | Earlier agent | Later agents | Conventions established, interfaces defined | | Orchestrator | All agents | Shared context, boundary assignments | | Any agent | Orchestrator β†’ All others | Resource claims, conflict warnings | -| TaskList | All agents | Current in_progress work, blockers, completed decisions | ### Pre-Parallel Coordination Check @@ -521,6 +500,18 @@ After each specialist completes work: This transforms implicit knowledge into explicit coordination, reducing "surprise" conflicts. +### Task-Based Coordination + +The orchestrator uses the Task system for S2 coordination: + +1. **Create agent Tasks before dispatching agents**: Each agent gets a Task created via `TaskCreate` before dispatch. This makes all active agents visible in `TaskList`. + +2. **Use TaskList for conflict detection**: Before dispatching parallel agents, check `TaskList` for in-progress agent Tasks that may conflict with new work (shared files, shared interfaces). + +3. **Use TaskList for parallel agent visibility**: During execution, `TaskList` shows all active agents, their subjects (describing their work), and their status. This enables the orchestrator to detect when agents may be working on overlapping concerns. + +4. **Orchestrator owns all Task coordination**: Agents do not see each other's Tasks. The orchestrator is the sole coordinator β€” it reads Task state and injects relevant context into agent prompts. + --- ## S1 Autonomy & Recursion @@ -641,6 +632,96 @@ For full protocol details, see [algedonic.md](algedonic.md). --- +## Task Hierarchy + +The Task system provides workflow visibility via Claude Code's native Task tools (TaskCreate, TaskUpdate, TaskGet, TaskList). The orchestrator owns ALL Task operations; agents communicate via structured text. + +> **Critical Platform Constraint**: Sub-agents spawned via Claude Code's Task tool do NOT have access to TaskCreate, TaskUpdate, TaskGet, or TaskList. Only the parent orchestrator process has these tools. + +### Hierarchy Structure + +``` +[Feature Task] "Implement user authentication" +β”œβ”€β”€ [Phase Task] "PREPARE: auth feature" (blockedBy: none) +β”‚ └── [Agent Task] "pact-preparer: research auth patterns" +β”œβ”€β”€ [Phase Task] "ARCHITECT: auth feature" (blockedBy: PREPARE) +β”‚ └── [Agent Task] "pact-architect: design auth service" +β”œβ”€β”€ [Phase Task] "CODE: auth feature" (blockedBy: ARCHITECT) +β”‚ β”œβ”€β”€ [Agent Task] "pact-backend-coder: auth endpoint" +β”‚ └── [Agent Task] "pact-frontend-coder: login form" +└── [Phase Task] "TEST: auth feature" (blockedBy: CODE, all CODE agents) + └── [Agent Task] "pact-test-engineer: auth tests" +``` + +### Creation Rules + +| Task Type | Created When | Created By | +|-----------|--------------|------------| +| Feature Task | Workflow start | Orchestrator | +| Phase Tasks | Workflow start (upfront) | Orchestrator | +| Agent Tasks | Phase begins (dynamic) | Orchestrator | +| Blocker Tasks | Agent reports blocker via text | Orchestrator | +| Algedonic Tasks | Agent reports signal via text | Orchestrator | + +### Dependency Model + +- **Unify on `blockedBy`**: All dependencies expressed via `blockedBy` +- **Phase-to-phase**: Strict chain (PREPARE β†’ ARCHITECT β†’ CODE β†’ TEST) +- **Agent-to-phase**: Agent tasks block the next phase +- **Skipped phases**: Created and immediately marked `completed` with note + +### Naming Convention + +- Feature: `"{verb} {feature}"` +- Phase: `"{PHASE}: {feature-slug}"` +- Agent: `"{agent-type}: {work-description}"` +- Blocker: `"Resolve: {description}"` +- Algedonic: `"⚠️ [HALT|ALERT]: {category}"` + +### VSM-Task Mapping + +| VSM System | Task System Role | +|------------|------------------| +| **S1 (Operations)** | Agents report status via structured text; do NOT call Task tools | +| **S2 (Coordination)** | Orchestrator uses TaskList for conflict detection, parallel agent visibility | +| **S3 (Orchestrator)** | Creates hierarchy, sets dependencies, translates agent reports into Task state | +| **S4 (Intelligence)** | Creates new Tasks on adaptation, deletes obsolete Tasks | +| **S5 (Policy)** | TaskList provides audit trail, user visibility via `/tasks` | + +### Orchestrator Dispatch Lifecycle + +| Event | Orchestrator Action | +|-------|---------------------| +| Before dispatching agent | `TaskCreate(subject="...")` β†’ gets `task_id` | +| Immediately after dispatch | `TaskUpdate(taskId=task_id, status="in_progress")` | +| Agent completes (handoff received) | `TaskUpdate(taskId=task_id, status="completed", metadata={...})` | +| Agent reports blocker | `TaskCreate(subject="Resolve: {desc}")` then `TaskUpdate(taskId=task_id, addBlockedBy=[blocker_id])` | +| Agent emits algedonic signal | `TaskCreate(subject="⚠️ [HALT|ALERT]: {cat}")` then amplify scope | + +### Tool Selection Guide + +| Need | Tool | Example | +|------|------|---------| +| Create a new task | `TaskCreate` | Create agent task when phase begins | +| Update status, dependencies, metadata | `TaskUpdate` | Mark task completed, add blockedBy | +| See all tasks and their states | `TaskList` | Monitor progress, detect signals, audit | +| Get full details of one task | `TaskGet` | Read handoff metadata, blocker context | + +### Agent Reporting (Text-Based) + +Agents do NOT call Task tools. They report via structured text: + +| Agent Event | Agent Action | +|-------------|-------------| +| Start | Begin working (orchestrator already marked task in_progress) | +| Blocker | Stop immediately, report: `BLOCKER: {description}` | +| Viability threat | Stop immediately, report: `⚠️ ALGEDONIC [HALT|ALERT]: {category} β€” {description}` | +| Completion | End response with structured HANDOFF | + +The orchestrator translates agent text into Task tool calls. + +--- + ## Variety Management Variety = complexity that must be matched with response capacity. Assess task variety before choosing a workflow. @@ -857,82 +938,6 @@ Keep it brief. No templates required. --- -## Task Hierarchy - -This document explains how PACT uses Claude Code's Task system to track work at multiple levels. - -### Hierarchy Levels - -``` -Feature Task (created by orchestrator) -β”œβ”€β”€ Phase Tasks (PREPARE, ARCHITECT, CODE, TEST) -β”‚ β”œβ”€β”€ Agent Task 1 (specialist work) -β”‚ β”œβ”€β”€ Agent Task 2 (parallel specialist) -β”‚ └── Agent Task 3 (parallel specialist) -└── Review Task (peer-review phase) -``` - -### Task Ownership - -| Level | Created By | Owned By | Lifecycle | -|-------|------------|----------|-----------| -| Feature | Orchestrator | Orchestrator | Spans entire workflow | -| Phase | Orchestrator | Orchestrator | Active during phase | -| Agent | Orchestrator | Specialist | Completed when specialist returns | - -### Task States - -Tasks progress through: `pending` β†’ `in_progress` β†’ `completed` - -- **pending**: Created but not started -- **in_progress**: Active work underway -- **completed**: Work finished (success or documented failure) - -### Blocking Relationships - -Use `addBlockedBy` to express dependencies: - -``` -CODE phase task -β”œβ”€β”€ blockedBy: [ARCHITECT task ID] -└── Agent tasks within CODE - └── blockedBy: [CODE phase task ID] -``` - -### Metadata Conventions - -Agent tasks include metadata for context: - -```json -{ - "phase": "CODE", - "domain": "backend", - "feature": "user-auth", - "handoff": { - "produced": ["src/auth.ts"], - "uncertainty": ["token refresh edge cases"] - } -} -``` - -### Integration with PACT Signals - -- **Algedonic signals**: Emit via task metadata or direct escalation -- **Variety signals**: Note in task metadata when complexity differs from estimate -- **Handoff**: Store structured handoff in task metadata on completion - -### Example Flow - -1. Orchestrator creates Feature task: "Implement user authentication" (parent container) -2. Orchestrator creates PREPARE phase task under the Feature task -3. Orchestrator dispatches pact-preparer with agent task (blocked by PREPARE phase task) -4. Preparer completes, updates task to completed with handoff metadata -5. Orchestrator marks PREPARE complete, creates ARCHITECT phase task -6. Orchestrator creates CODE phase task (blocked by ARCHITECT phase task) -7. Pattern continues through remaining phases - ---- - ## Backend ↔ Database Boundary **Sequence**: Database delivers schema β†’ Backend implements ORM. diff --git a/pact-plugin/protocols/pact-s2-coordination.md b/pact-plugin/protocols/pact-s2-coordination.md index d27068c..66e6041 100644 --- a/pact-plugin/protocols/pact-s2-coordination.md +++ b/pact-plugin/protocols/pact-s2-coordination.md @@ -2,26 +2,6 @@ The coordination layer enables parallel agent operation without conflicts. S2 is **proactive** (prevents conflicts) not just **reactive** (resolves conflicts). Apply these protocols whenever multiple agents work concurrently. -### Task System Integration - -With PACT Task integration, the TaskList serves as a **shared state mechanism** for coordination: - -| Use Case | How TaskList Helps | -|----------|-------------------| -| **Conflict detection** | Query TaskList to see what files/components other agents are working on | -| **Parallel agent visibility** | All in_progress agent Tasks visible via TaskList | -| **Convention propagation** | First agent's metadata (decisions, patterns) queryable by later agents | -| **Resource claims** | Agent Tasks can include metadata about claimed resources | - -**Coordination via Tasks:** -``` -Before parallel dispatch: -1. TaskList β†’ check for in_progress agents on same files -2. If conflict detected β†’ sequence or assign boundaries -3. Dispatch agents with Task IDs -4. Monitor via TaskList for completion/blockers -``` - ### Information Flows S2 manages information flow between agents: @@ -31,7 +11,6 @@ S2 manages information flow between agents: | Earlier agent | Later agents | Conventions established, interfaces defined | | Orchestrator | All agents | Shared context, boundary assignments | | Any agent | Orchestrator β†’ All others | Resource claims, conflict warnings | -| TaskList | All agents | Current in_progress work, blockers, completed decisions | ### Pre-Parallel Coordination Check @@ -157,6 +136,18 @@ After each specialist completes work: This transforms implicit knowledge into explicit coordination, reducing "surprise" conflicts. +### Task-Based Coordination + +The orchestrator uses the Task system for S2 coordination: + +1. **Create agent Tasks before dispatching agents**: Each agent gets a Task created via `TaskCreate` before dispatch. This makes all active agents visible in `TaskList`. + +2. **Use TaskList for conflict detection**: Before dispatching parallel agents, check `TaskList` for in-progress agent Tasks that may conflict with new work (shared files, shared interfaces). + +3. **Use TaskList for parallel agent visibility**: During execution, `TaskList` shows all active agents, their subjects (describing their work), and their status. This enables the orchestrator to detect when agents may be working on overlapping concerns. + +4. **Orchestrator owns all Task coordination**: Agents do not see each other's Tasks. The orchestrator is the sole coordinator β€” it reads Task state and injects relevant context into agent prompts. + --- ## Backend ↔ Database Boundary diff --git a/pact-plugin/protocols/pact-task-hierarchy.md b/pact-plugin/protocols/pact-task-hierarchy.md index 9231ecd..9167ff0 100644 --- a/pact-plugin/protocols/pact-task-hierarchy.md +++ b/pact-plugin/protocols/pact-task-hierarchy.md @@ -1,74 +1,89 @@ ## Task Hierarchy -This document explains how PACT uses Claude Code's Task system to track work at multiple levels. +The Task system provides workflow visibility via Claude Code's native Task tools (TaskCreate, TaskUpdate, TaskGet, TaskList). The orchestrator owns ALL Task operations; agents communicate via structured text. -### Hierarchy Levels +> **Critical Platform Constraint**: Sub-agents spawned via Claude Code's Task tool do NOT have access to TaskCreate, TaskUpdate, TaskGet, or TaskList. Only the parent orchestrator process has these tools. + +### Hierarchy Structure ``` -Feature Task (created by orchestrator) -β”œβ”€β”€ Phase Tasks (PREPARE, ARCHITECT, CODE, TEST) -β”‚ β”œβ”€β”€ Agent Task 1 (specialist work) -β”‚ β”œβ”€β”€ Agent Task 2 (parallel specialist) -β”‚ └── Agent Task 3 (parallel specialist) -└── Review Task (peer-review phase) +[Feature Task] "Implement user authentication" +β”œβ”€β”€ [Phase Task] "PREPARE: auth feature" (blockedBy: none) +β”‚ └── [Agent Task] "pact-preparer: research auth patterns" +β”œβ”€β”€ [Phase Task] "ARCHITECT: auth feature" (blockedBy: PREPARE) +β”‚ └── [Agent Task] "pact-architect: design auth service" +β”œβ”€β”€ [Phase Task] "CODE: auth feature" (blockedBy: ARCHITECT) +β”‚ β”œβ”€β”€ [Agent Task] "pact-backend-coder: auth endpoint" +β”‚ └── [Agent Task] "pact-frontend-coder: login form" +└── [Phase Task] "TEST: auth feature" (blockedBy: CODE, all CODE agents) + └── [Agent Task] "pact-test-engineer: auth tests" ``` -### Task Ownership +### Creation Rules -| Level | Created By | Owned By | Lifecycle | -|-------|------------|----------|-----------| -| Feature | Orchestrator | Orchestrator | Spans entire workflow | -| Phase | Orchestrator | Orchestrator | Active during phase | -| Agent | Orchestrator | Specialist | Completed when specialist returns | +| Task Type | Created When | Created By | +|-----------|--------------|------------| +| Feature Task | Workflow start | Orchestrator | +| Phase Tasks | Workflow start (upfront) | Orchestrator | +| Agent Tasks | Phase begins (dynamic) | Orchestrator | +| Blocker Tasks | Agent reports blocker via text | Orchestrator | +| Algedonic Tasks | Agent reports signal via text | Orchestrator | -### Task States +### Dependency Model -Tasks progress through: `pending` β†’ `in_progress` β†’ `completed` +- **Unify on `blockedBy`**: All dependencies expressed via `blockedBy` +- **Phase-to-phase**: Strict chain (PREPARE β†’ ARCHITECT β†’ CODE β†’ TEST) +- **Agent-to-phase**: Agent tasks block the next phase +- **Skipped phases**: Created and immediately marked `completed` with note -- **pending**: Created but not started -- **in_progress**: Active work underway -- **completed**: Work finished (success or documented failure) +### Naming Convention -### Blocking Relationships +- Feature: `"{verb} {feature}"` +- Phase: `"{PHASE}: {feature-slug}"` +- Agent: `"{agent-type}: {work-description}"` +- Blocker: `"Resolve: {description}"` +- Algedonic: `"⚠️ [HALT|ALERT]: {category}"` -Use `addBlockedBy` to express dependencies: +### VSM-Task Mapping -``` -CODE phase task -β”œβ”€β”€ blockedBy: [ARCHITECT task ID] -└── Agent tasks within CODE - └── blockedBy: [CODE phase task ID] -``` +| VSM System | Task System Role | +|------------|------------------| +| **S1 (Operations)** | Agents report status via structured text; do NOT call Task tools | +| **S2 (Coordination)** | Orchestrator uses TaskList for conflict detection, parallel agent visibility | +| **S3 (Orchestrator)** | Creates hierarchy, sets dependencies, translates agent reports into Task state | +| **S4 (Intelligence)** | Creates new Tasks on adaptation, deletes obsolete Tasks | +| **S5 (Policy)** | TaskList provides audit trail, user visibility via `/tasks` | -### Metadata Conventions +### Orchestrator Dispatch Lifecycle -Agent tasks include metadata for context: +| Event | Orchestrator Action | +|-------|---------------------| +| Before dispatching agent | `TaskCreate(subject="...")` β†’ gets `task_id` | +| Immediately after dispatch | `TaskUpdate(taskId=task_id, status="in_progress")` | +| Agent completes (handoff received) | `TaskUpdate(taskId=task_id, status="completed", metadata={...})` | +| Agent reports blocker | `TaskCreate(subject="Resolve: {desc}")` then `TaskUpdate(taskId=task_id, addBlockedBy=[blocker_id])` | +| Agent emits algedonic signal | `TaskCreate(subject="⚠️ [HALT|ALERT]: {cat}")` then amplify scope | -```json -{ - "phase": "CODE", - "domain": "backend", - "feature": "user-auth", - "handoff": { - "produced": ["src/auth.ts"], - "uncertainty": ["token refresh edge cases"] - } -} -``` +### Tool Selection Guide + +| Need | Tool | Example | +|------|------|---------| +| Create a new task | `TaskCreate` | Create agent task when phase begins | +| Update status, dependencies, metadata | `TaskUpdate` | Mark task completed, add blockedBy | +| See all tasks and their states | `TaskList` | Monitor progress, detect signals, audit | +| Get full details of one task | `TaskGet` | Read handoff metadata, blocker context | -### Integration with PACT Signals +### Agent Reporting (Text-Based) -- **Algedonic signals**: Emit via task metadata or direct escalation -- **Variety signals**: Note in task metadata when complexity differs from estimate -- **Handoff**: Store structured handoff in task metadata on completion +Agents do NOT call Task tools. They report via structured text: -### Example Flow +| Agent Event | Agent Action | +|-------------|-------------| +| Start | Begin working (orchestrator already marked task in_progress) | +| Blocker | Stop immediately, report: `BLOCKER: {description}` | +| Viability threat | Stop immediately, report: `⚠️ ALGEDONIC [HALT|ALERT]: {category} β€” {description}` | +| Completion | End response with structured HANDOFF | -1. Orchestrator creates Feature task: "Implement user authentication" (parent container) -2. Orchestrator creates PREPARE phase task under the Feature task -3. Orchestrator dispatches pact-preparer with agent task (blocked by PREPARE phase task) -4. Preparer completes, updates task to completed with handoff metadata -5. Orchestrator marks PREPARE complete, creates ARCHITECT phase task -6. Orchestrator creates CODE phase task (blocked by ARCHITECT phase task) -7. Pattern continues through remaining phases +The orchestrator translates agent text into Task tool calls. +--- diff --git a/pact-plugin/skills/README.md b/pact-plugin/skills/README.md deleted file mode 100644 index 505cde1..0000000 --- a/pact-plugin/skills/README.md +++ /dev/null @@ -1,9 +0,0 @@ -# PACT Skills - -## Naming Convention -- Use kebab-case: `pact-{domain}-{function}` -- Prefix with `pact-` for framework skills -- Examples: `pact-task-tracking`, `pact-memory`, `pact-coding-standards` - -## Auto-Loading -Skills listed in an agent's frontmatter `skills:` field are automatically loaded when the agent is invoked via the Task tool. diff --git a/pact-plugin/skills/pact-task-tracking/SKILL.md b/pact-plugin/skills/pact-task-tracking/SKILL.md index 38bed0d..fcb9bc1 100644 --- a/pact-plugin/skills/pact-task-tracking/SKILL.md +++ b/pact-plugin/skills/pact-task-tracking/SKILL.md @@ -2,69 +2,68 @@ name: pact-task-tracking description: | Task tracking protocol for PACT specialist agents. Auto-loaded via agent frontmatter. - Defines how to report progress, blockers, and completion status via TaskCreate/TaskUpdate tools. + Defines how to report progress, blockers, and completion status to the orchestrator. + Use when: working as a PACT specialist agent dispatched by the orchestrator. + Triggers: task tracking, handoff, blocker, algedonic, agent reporting --- -# Task Tracking Protocol +# Agent Reporting Protocol -> **Architecture**: See [pact-task-hierarchy.md](../../protocols/pact-task-hierarchy.md) for the full hierarchy model. +You are a PACT specialist agent. The orchestrator manages all workflow state on your behalf. Your job is to do the work and report results through structured text. -> **Reference**: See [Claude Code Task System](https://docs.anthropic.com/en/docs/claude-code/tasks) for full API documentation. +## Platform Constraint -> **Note**: The orchestrator passes your assigned task ID when dispatching you. Use this ID in all TaskUpdate calls. +You do **NOT** have access to Task tools (TaskCreate, TaskUpdate, TaskGet, TaskList). These tools are only available to the parent orchestrator process. Do not attempt to call them. -You have been assigned Task ID: {task_id} +The orchestrator: +- Created a Task for your work before dispatching you +- Marked it `in_progress` when you started +- Will mark it `completed` when you return your response +- Will create blocker/algedonic Tasks if you report them -## On Start +You do not need to manage Task state. Focus on your work. -Before any other work, update your task status: +## Structured Handoff (Required) + +Every response you return MUST end with a structured handoff block. This is how the orchestrator knows what you accomplished. ``` -TaskUpdate(taskId="{task_id}", status="in_progress") +**HANDOFF** +1. **Produced**: [files created or modified] +2. **Key context**: [decisions made, patterns used, important state] +3. **Areas of uncertainty**: [things you are unsure about, where bugs might hide] +4. **Open questions**: [questions for the orchestrator or user] ``` -## On Blocker +Include a handoff even if your work is incomplete (due to a blocker or signal). -If you cannot proceed: +## Blocker Protocol -1. Create a blocker task: - ``` - TaskCreate(subject="Resolve: {description}", metadata={"type": "blocker"}) - ``` -2. Link it to your task: - ``` - TaskUpdate(taskId="{task_id}", addBlockedBy=[blocker_id]) - ``` -3. Stop work and report: "BLOCKER: {description}" +When you cannot proceed: -## On Algedonic Signal +1. **STOP work immediately** -- do not attempt workarounds +2. Report: `BLOCKER: {description}` +3. Include: what you tried, why it failed, what is needed to proceed +4. End with a partial handoff -When you detect a viability threat: +The orchestrator will triage the blocker. Do not try to solve it yourself. -1. Create an algedonic task: - ``` - TaskCreate(subject="⚠️ [HALT|ALERT]: {category}", metadata={"type": "algedonic", "level": "...", "category": "..."}) - ``` -2. Link it to your task: - ``` - TaskUpdate(taskId="{task_id}", addBlockedBy=[algedonic_id]) - ``` -3. Stop immediately -4. Report signal to orchestrator +## Algedonic Signal Protocol -## On Completion +When you detect a viability threat (security vulnerability, data loss risk, ethical concern, quality crisis): -After all work is done: +1. **STOP immediately** -- do not continue any work +2. Report: `ALGEDONIC [HALT|ALERT]: {category} -- {description}` +3. Do **NOT** continue any work after emitting this signal +4. End with a partial handoff -``` -TaskUpdate( - taskId="{task_id}", - status="completed", - metadata={ - "produced": ["file1.ts", "file2.ts"], - "decisions": ["key decisions made"], - "uncertainties": ["areas needing review"] - } -) -``` +The orchestrator will escalate to the user. Your work is done until the signal is resolved. + +## Summary +| Situation | Action | +|-----------|--------| +| Work complete | End with full **HANDOFF** | +| Cannot proceed | STOP, report `BLOCKER:`, partial HANDOFF | +| Viability threat | STOP, report `ALGEDONIC [HALT\|ALERT]:`, partial HANDOFF | +| Need Task tools | You do not have them -- the orchestrator handles Task state | diff --git a/pact-plugin/skills/pact-task-tracking/test_skill_loading.py b/pact-plugin/skills/pact-task-tracking/test_skill_loading.py deleted file mode 100644 index 8324593..0000000 --- a/pact-plugin/skills/pact-task-tracking/test_skill_loading.py +++ /dev/null @@ -1,89 +0,0 @@ -""" -/Users/mj/Sites/collab/PACT-prompt/pact-plugin/skills/pact-task-tracking/test_skill_loading.py - -Tests for verifying the pact-task-tracking skill file structure and content. -Ensures SKILL.md exists, has valid YAML frontmatter, and includes key sections. -""" - -import pytest -from pathlib import Path -import yaml - - -SKILL_DIR = Path(__file__).parent -SKILL_FILE = SKILL_DIR / "SKILL.md" - - -@pytest.fixture -def skill_content(): - """Load the skill file content.""" - return SKILL_FILE.read_text() - - -class TestSkillFileExists: - """Test that the skill file exists.""" - - def test_skill_md_exists(self): - """SKILL.md file must exist in the skill directory.""" - assert SKILL_FILE.exists(), f"SKILL.md not found at {SKILL_FILE}" - - def test_skill_md_is_file(self): - """SKILL.md must be a regular file, not a directory.""" - assert SKILL_FILE.is_file(), f"{SKILL_FILE} exists but is not a file" - - -class TestYamlFrontmatter: - """Test that YAML frontmatter is valid and has required fields.""" - - @pytest.fixture - def frontmatter(self, skill_content): - """Extract and parse YAML frontmatter from skill file.""" - if not skill_content.startswith("---"): - pytest.fail("SKILL.md must start with YAML frontmatter (---)") - - # Find the closing --- - end_marker = skill_content.find("---", 3) - if end_marker == -1: - pytest.fail("SKILL.md has unclosed YAML frontmatter") - - yaml_content = skill_content[3:end_marker].strip() - return yaml.safe_load(yaml_content) - - def test_has_name_field(self, frontmatter): - """Frontmatter must include 'name' field.""" - assert "name" in frontmatter, "YAML frontmatter must include 'name' field" - assert frontmatter["name"], "'name' field must not be empty" - - def test_has_description_field(self, frontmatter): - """Frontmatter must include 'description' field.""" - assert "description" in frontmatter, "YAML frontmatter must include 'description' field" - assert frontmatter["description"], "'description' field must not be empty" - - def test_name_matches_directory(self, frontmatter): - """Skill name should match the directory name.""" - expected_name = SKILL_DIR.name - assert frontmatter["name"] == expected_name, ( - f"Skill name '{frontmatter['name']}' should match directory name '{expected_name}'" - ) - - -class TestKeyContentSections: - """Test that the skill content includes required sections.""" - - def test_has_on_start_section(self, skill_content): - """Skill must include 'On Start' section.""" - assert "## On Start" in skill_content, "SKILL.md must include '## On Start' section" - - def test_has_on_blocker_section(self, skill_content): - """Skill must include 'On Blocker' section.""" - assert "## On Blocker" in skill_content, "SKILL.md must include '## On Blocker' section" - - def test_has_on_completion_section(self, skill_content): - """Skill must include 'On Completion' section.""" - assert "## On Completion" in skill_content, "SKILL.md must include '## On Completion' section" - - def test_task_id_placeholder_present(self, skill_content): - """Skill must include task_id placeholder for orchestrator injection.""" - assert "{task_id}" in skill_content, ( - "SKILL.md must include '{task_id}' placeholder for task ID injection" - ) diff --git a/pact-plugin/tests/test_task_integration.py b/pact-plugin/tests/test_task_integration.py deleted file mode 100644 index b230f5f..0000000 --- a/pact-plugin/tests/test_task_integration.py +++ /dev/null @@ -1,784 +0,0 @@ -""" -Integration tests for Task system functions. - -Tests the Task-first code path that reads workflow state from the Claude Task system -for post-compaction recovery. These functions are the primary state source, with -checkpoint files serving as fallback. - -Location: pact-plugin/tests/test_task_integration.py - -Core Task utilities are in hooks/shared/task_utils.py. -build_refresh_from_tasks and main remain in compaction_refresh.py. -""" - -import json -import os -import sys -from pathlib import Path -from typing import Any - -import pytest - -# Add hooks directory to path for imports -sys.path.insert(0, str(Path(__file__).parent.parent / "hooks")) - - -# ============================================================================= -# Fixtures for Task Testing -# ============================================================================= - -@pytest.fixture -def mock_tasks_dir(tmp_path: Path, monkeypatch): - """ - Create mock ~/.claude/tasks/{sessionId}/ structure. - - Returns: - Path to the tasks directory for the test session - """ - session_id = "test-session-123" - tasks_dir = tmp_path / ".claude" / "tasks" / session_id - tasks_dir.mkdir(parents=True) - monkeypatch.setenv("CLAUDE_SESSION_ID", session_id) - monkeypatch.setenv("HOME", str(tmp_path)) - - # Patch Path.home() to return our temp directory - monkeypatch.setattr(Path, "home", lambda: tmp_path) - - return tasks_dir - - -@pytest.fixture -def make_task(): - """Factory fixture to create task dictionaries.""" - def _make( - task_id: str, - subject: str, - status: str = "in_progress", - blocked_by: list[str] | None = None, - metadata: dict[str, Any] | None = None, - ) -> dict[str, Any]: - task = { - "id": task_id, - "subject": subject, - "status": status, - } - if blocked_by: - task["blockedBy"] = blocked_by - if metadata: - task["metadata"] = metadata - return task - return _make - - -@pytest.fixture -def sample_task_list(make_task) -> list[dict[str, Any]]: - """ - Create a realistic task list representing an in-progress orchestration. - - Structure: - - Feature task: "Implement user authentication" - - Phase tasks: PREPARE (completed), ARCHITECT (completed), CODE (in_progress) - - Agent tasks: backend-coder (in_progress) - """ - return [ - # Feature task (top-level, not blocked by anything) - make_task( - task_id="task-001", - subject="Implement user authentication", - status="in_progress", - ), - # Completed phase tasks - make_task( - task_id="task-002", - subject="PREPARE: user-authentication", - status="completed", - blocked_by=["task-001"], - ), - make_task( - task_id="task-003", - subject="ARCHITECT: user-authentication", - status="completed", - blocked_by=["task-002"], - ), - # Current phase task (in_progress) - make_task( - task_id="task-004", - subject="CODE: user-authentication", - status="in_progress", - blocked_by=["task-003"], - ), - # Active agent task - make_task( - task_id="task-005", - subject="pact-backend-coder: Implement auth endpoint", - status="in_progress", - blocked_by=["task-004"], - ), - ] - - -# ============================================================================= -# Tests for get_task_list() -# ============================================================================= - -class TestGetTaskList: - """Tests for get_task_list() function.""" - - def test_reads_valid_json_files(self, mock_tasks_dir, make_task): - """Test reading task list from valid JSON files.""" - from shared.task_utils import get_task_list - - # Create task files - task1 = make_task("task-1", "Feature task", "in_progress") - task2 = make_task("task-2", "Agent task", "pending") - - (mock_tasks_dir / "task-1.json").write_text(json.dumps(task1)) - (mock_tasks_dir / "task-2.json").write_text(json.dumps(task2)) - - result = get_task_list() - - assert result is not None - assert len(result) == 2 - subjects = {t["subject"] for t in result} - assert "Feature task" in subjects - assert "Agent task" in subjects - - def test_returns_none_for_empty_directory(self, mock_tasks_dir): - """Test returns None when tasks directory is empty.""" - from shared.task_utils import get_task_list - - # Directory exists but is empty - result = get_task_list() - - assert result is None - - def test_returns_none_for_missing_directory(self, tmp_path, monkeypatch): - """Test returns None when tasks directory doesn't exist.""" - from shared.task_utils import get_task_list - - session_id = "nonexistent-session" - monkeypatch.setenv("CLAUDE_SESSION_ID", session_id) - monkeypatch.setattr(Path, "home", lambda: tmp_path) - - result = get_task_list() - - assert result is None - - def test_skips_malformed_json_files(self, mock_tasks_dir, make_task): - """Test skips malformed JSON files and continues with valid ones.""" - from shared.task_utils import get_task_list - - # Create one valid and one malformed file - valid_task = make_task("task-1", "Valid task", "in_progress") - (mock_tasks_dir / "task-1.json").write_text(json.dumps(valid_task)) - (mock_tasks_dir / "task-2.json").write_text("{ invalid json") - (mock_tasks_dir / "task-3.json").write_text("") - - result = get_task_list() - - assert result is not None - assert len(result) == 1 - assert result[0]["subject"] == "Valid task" - - def test_returns_none_when_session_id_not_set(self, tmp_path, monkeypatch): - """Test returns None when CLAUDE_SESSION_ID is not set.""" - from shared.task_utils import get_task_list - - # Clear session ID environment variable - monkeypatch.delenv("CLAUDE_SESSION_ID", raising=False) - monkeypatch.delenv("CLAUDE_CODE_TASK_LIST_ID", raising=False) - monkeypatch.setattr(Path, "home", lambda: tmp_path) - - result = get_task_list() - - assert result is None - - def test_uses_task_list_id_when_provided(self, tmp_path, monkeypatch, make_task): - """Test uses CLAUDE_CODE_TASK_LIST_ID when available.""" - from shared.task_utils import get_task_list - - # Setup with different session_id and task_list_id - session_id = "session-abc" - task_list_id = "shared-task-list-xyz" - - monkeypatch.setenv("CLAUDE_SESSION_ID", session_id) - monkeypatch.setenv("CLAUDE_CODE_TASK_LIST_ID", task_list_id) - monkeypatch.setattr(Path, "home", lambda: tmp_path) - - # Create tasks directory using task_list_id - tasks_dir = tmp_path / ".claude" / "tasks" / task_list_id - tasks_dir.mkdir(parents=True) - - task = make_task("task-1", "Shared task", "in_progress") - (tasks_dir / "task-1.json").write_text(json.dumps(task)) - - result = get_task_list() - - assert result is not None - assert len(result) == 1 - assert result[0]["subject"] == "Shared task" - - def test_handles_non_json_files_gracefully(self, mock_tasks_dir, make_task): - """Test ignores non-JSON files in tasks directory.""" - from shared.task_utils import get_task_list - - # Create a valid task and some non-JSON files - task = make_task("task-1", "Valid task", "in_progress") - (mock_tasks_dir / "task-1.json").write_text(json.dumps(task)) - (mock_tasks_dir / "readme.txt").write_text("This is not a task") - (mock_tasks_dir / ".hidden").write_text("Hidden file") - - result = get_task_list() - - assert result is not None - assert len(result) == 1 - assert result[0]["subject"] == "Valid task" - - -# ============================================================================= -# Tests for find_feature_task() -# ============================================================================= - -class TestFindFeatureTask: - """Tests for find_feature_task() function.""" - - def test_finds_top_level_feature_task(self, sample_task_list): - """Test finds feature task that has no blockedBy.""" - from shared.task_utils import find_feature_task - - result = find_feature_task(sample_task_list) - - assert result is not None - assert result["id"] == "task-001" - assert result["subject"] == "Implement user authentication" - - def test_filters_out_phase_tasks(self, make_task): - """Test excludes tasks with phase prefixes from feature detection.""" - from shared.task_utils import find_feature_task - - tasks = [ - make_task("task-1", "PREPARE: some-feature", "in_progress"), - make_task("task-2", "ARCHITECT: some-feature", "in_progress"), - make_task("task-3", "CODE: some-feature", "in_progress"), - make_task("task-4", "TEST: some-feature", "in_progress"), - make_task("task-5", "Review: some-feature", "in_progress"), - ] - - result = find_feature_task(tasks) - - assert result is None - - def test_returns_none_when_no_feature_task(self, make_task): - """Test returns None when no suitable feature task exists.""" - from shared.task_utils import find_feature_task - - # Only blocked tasks - tasks = [ - make_task("task-1", "Agent work", "in_progress", blocked_by=["task-0"]), - make_task("task-2", "More work", "pending", blocked_by=["task-1"]), - ] - - result = find_feature_task(tasks) - - assert result is None - - def test_returns_none_for_empty_task_list(self): - """Test returns None for empty task list.""" - from shared.task_utils import find_feature_task - - result = find_feature_task([]) - - assert result is None - - def test_prefers_in_progress_over_pending(self, make_task): - """Test prefers in_progress feature tasks over pending ones.""" - from shared.task_utils import find_feature_task - - tasks = [ - make_task("task-1", "Pending feature", "pending"), - make_task("task-2", "Active feature", "in_progress"), - ] - - result = find_feature_task(tasks) - - # The first matching in_progress task should be returned - # Both are valid, but it should find one - assert result is not None - assert result["status"] in ("in_progress", "pending") - - def test_handles_tasks_without_id(self, make_task): - """Test handles tasks missing the id field gracefully.""" - from shared.task_utils import find_feature_task - - tasks = [ - {"subject": "Missing id", "status": "in_progress"}, - make_task("task-1", "Valid feature", "in_progress"), - ] - - result = find_feature_task(tasks) - - assert result is not None - assert result.get("id") == "task-1" - - -# ============================================================================= -# Tests for find_current_phase() -# ============================================================================= - -class TestFindCurrentPhase: - """Tests for find_current_phase() function.""" - - def test_finds_in_progress_phase(self, sample_task_list): - """Test finds phase task with status in_progress.""" - from shared.task_utils import find_current_phase - - result = find_current_phase(sample_task_list) - - assert result is not None - assert result["id"] == "task-004" - assert result["subject"] == "CODE: user-authentication" - assert result["status"] == "in_progress" - - def test_returns_none_with_no_in_progress_phases(self, make_task): - """Test returns None when no phase is in_progress.""" - from shared.task_utils import find_current_phase - - tasks = [ - make_task("task-1", "PREPARE: feature", "completed"), - make_task("task-2", "ARCHITECT: feature", "completed"), - make_task("task-3", "Feature task", "in_progress"), # Not a phase - ] - - result = find_current_phase(tasks) - - assert result is None - - def test_detects_all_phase_prefixes(self, make_task): - """Test detects all valid phase prefixes.""" - from shared.task_utils import find_current_phase - - for phase_prefix in ["PREPARE:", "ARCHITECT:", "CODE:", "TEST:"]: - tasks = [make_task("task-1", f"{phase_prefix} feature", "in_progress")] - result = find_current_phase(tasks) - assert result is not None, f"Failed to detect {phase_prefix}" - assert result["id"] == "task-1" - - def test_returns_none_for_empty_list(self): - """Test returns None for empty task list.""" - from shared.task_utils import find_current_phase - - result = find_current_phase([]) - - assert result is None - - def test_ignores_completed_phase_tasks(self, make_task): - """Test ignores completed and pending phase tasks.""" - from shared.task_utils import find_current_phase - - tasks = [ - make_task("task-1", "PREPARE: feature", "completed"), - make_task("task-2", "ARCHITECT: feature", "pending"), - ] - - result = find_current_phase(tasks) - - assert result is None - - -# ============================================================================= -# Tests for find_active_agents() -# ============================================================================= - -class TestFindActiveAgents: - """Tests for find_active_agents() function.""" - - def test_finds_in_progress_agent_tasks(self, sample_task_list): - """Test finds agent tasks with status in_progress.""" - from shared.task_utils import find_active_agents - - result = find_active_agents(sample_task_list) - - assert len(result) == 1 - assert result[0]["id"] == "task-005" - assert "pact-backend-coder" in result[0]["subject"].lower() - - def test_finds_multiple_active_agents(self, make_task): - """Test finds all active agent tasks when multiple exist.""" - from shared.task_utils import find_active_agents - - tasks = [ - make_task("task-1", "pact-backend-coder: API endpoint", "in_progress"), - make_task("task-2", "pact-frontend-coder: UI component", "in_progress"), - make_task("task-3", "pact-test-engineer: Integration tests", "in_progress"), - make_task("task-4", "pact-architect: Design review", "completed"), # Not active - ] - - result = find_active_agents(tasks) - - assert len(result) == 3 - agent_ids = {t["id"] for t in result} - assert agent_ids == {"task-1", "task-2", "task-3"} - - def test_returns_empty_list_with_no_active_agents(self, make_task): - """Test returns empty list when no agents are in_progress.""" - from shared.task_utils import find_active_agents - - tasks = [ - make_task("task-1", "pact-backend-coder: Work", "completed"), - make_task("task-2", "Feature task", "in_progress"), # Not an agent - ] - - result = find_active_agents(tasks) - - assert result == [] - - def test_detects_all_agent_prefixes(self, make_task): - """Test detects all valid agent type prefixes.""" - from shared.task_utils import find_active_agents - - agent_prefixes = [ - "pact-preparer:", - "pact-architect:", - "pact-backend-coder:", - "pact-frontend-coder:", - "pact-database-engineer:", - "pact-test-engineer:", - "pact-memory-agent:", - "pact-n8n:", - ] - - for prefix in agent_prefixes: - tasks = [make_task("task-1", f"{prefix} work item", "in_progress")] - result = find_active_agents(tasks) - assert len(result) == 1, f"Failed to detect {prefix}" - - def test_returns_empty_list_for_empty_input(self): - """Test returns empty list for empty task list.""" - from shared.task_utils import find_active_agents - - result = find_active_agents([]) - - assert result == [] - - def test_case_insensitive_agent_detection(self, make_task): - """Test agent detection is case-insensitive for subject.""" - from shared.task_utils import find_active_agents - - tasks = [ - make_task("task-1", "PACT-BACKEND-CODER: Uppercase", "in_progress"), - make_task("task-2", "Pact-Frontend-Coder: Mixed case", "in_progress"), - ] - - result = find_active_agents(tasks) - - # The implementation converts to lowercase for comparison - assert len(result) == 2 - - -# ============================================================================= -# Tests for find_blockers() -# ============================================================================= - -class TestFindBlockers: - """Tests for find_blockers() function.""" - - def test_finds_blocker_tasks(self, make_task): - """Test finds tasks with metadata.type == 'blocker'.""" - from shared.task_utils import find_blockers - - tasks = [ - make_task( - "task-1", - "Blocked: Missing API credentials", - "pending", - metadata={"type": "blocker"}, - ), - make_task("task-2", "Normal task", "in_progress"), - ] - - result = find_blockers(tasks) - - assert len(result) == 1 - assert result[0]["id"] == "task-1" - - def test_finds_algedonic_tasks(self, make_task): - """Test finds tasks with metadata.type == 'algedonic'.""" - from shared.task_utils import find_blockers - - tasks = [ - make_task( - "task-1", - "HALT: Security vulnerability detected", - "pending", - metadata={"type": "algedonic", "level": "HALT"}, - ), - ] - - result = find_blockers(tasks) - - assert len(result) == 1 - assert result[0]["metadata"]["level"] == "HALT" - - def test_filters_out_completed_blockers(self, make_task): - """Test excludes blockers that have been resolved (status: completed).""" - from shared.task_utils import find_blockers - - tasks = [ - make_task( - "task-1", - "Resolved blocker", - "completed", - metadata={"type": "blocker"}, - ), - make_task( - "task-2", - "Active blocker", - "pending", - metadata={"type": "blocker"}, - ), - ] - - result = find_blockers(tasks) - - assert len(result) == 1 - assert result[0]["id"] == "task-2" - - def test_returns_empty_list_with_no_blockers(self, make_task): - """Test returns empty list when no blocker/algedonic tasks exist.""" - from shared.task_utils import find_blockers - - tasks = [ - make_task("task-1", "Normal task", "in_progress"), - make_task("task-2", "Another task", "pending", metadata={"type": "normal"}), - ] - - result = find_blockers(tasks) - - assert result == [] - - def test_returns_empty_list_for_empty_input(self): - """Test returns empty list for empty task list.""" - from shared.task_utils import find_blockers - - result = find_blockers([]) - - assert result == [] - - def test_handles_tasks_without_metadata(self, make_task): - """Test handles tasks that have no metadata field.""" - from shared.task_utils import find_blockers - - tasks = [ - make_task("task-1", "No metadata task", "in_progress"), - {"id": "task-2", "subject": "Also no metadata", "status": "pending"}, - ] - - result = find_blockers(tasks) - - assert result == [] - - def test_finds_both_blockers_and_algedonics(self, make_task): - """Test finds both blocker and algedonic types together.""" - from shared.task_utils import find_blockers - - tasks = [ - make_task( - "task-1", - "Blocker task", - "pending", - metadata={"type": "blocker"}, - ), - make_task( - "task-2", - "Algedonic task", - "pending", - metadata={"type": "algedonic"}, - ), - ] - - result = find_blockers(tasks) - - assert len(result) == 2 - - -# ============================================================================= -# Tests for build_refresh_from_tasks() -# ============================================================================= - -class TestBuildRefreshFromTasks: - """Tests for build_refresh_from_tasks() function.""" - - def test_builds_complete_message(self, make_task): - """Test builds complete refresh message with all components.""" - from compaction_refresh import build_refresh_from_tasks - - feature = make_task("feat-1", "Implement auth", "in_progress") - phase = make_task("phase-1", "CODE: auth", "in_progress") - agents = [make_task("agent-1", "pact-backend-coder: work", "in_progress")] - blockers = [] - - result = build_refresh_from_tasks(feature, phase, agents, blockers) - - assert "[POST-COMPACTION CHECKPOINT]" in result - assert "Implement auth" in result - assert "CODE: auth" in result - assert "pact-backend-coder" in result - assert "Monitor active agents" in result - - def test_handles_missing_feature(self, make_task): - """Test handles None feature task gracefully.""" - from compaction_refresh import build_refresh_from_tasks - - phase = make_task("phase-1", "CODE: work", "in_progress") - agents = [] - - result = build_refresh_from_tasks(None, phase, agents, []) - - assert "Unable to identify feature task" in result - - def test_handles_missing_phase(self, make_task): - """Test handles None phase task gracefully.""" - from compaction_refresh import build_refresh_from_tasks - - feature = make_task("feat-1", "Feature", "in_progress") - - result = build_refresh_from_tasks(feature, None, [], []) - - assert "None detected" in result - - def test_includes_blocker_information(self, make_task): - """Test includes blocker details in message.""" - from compaction_refresh import build_refresh_from_tasks - - feature = make_task("feat-1", "Feature", "in_progress") - blockers = [ - make_task( - "block-1", - "Missing credentials", - "pending", - metadata={"type": "blocker", "level": "HALT"}, - ), - ] - - result = build_refresh_from_tasks(feature, None, [], blockers) - - assert "**BLOCKERS DETECTED:**" in result - assert "Missing credentials" in result - assert "HALT" in result - assert "Address blockers" in result - - def test_formats_multiple_agents(self, make_task): - """Test formats multiple active agents correctly.""" - from compaction_refresh import build_refresh_from_tasks - - feature = make_task("feat-1", "Feature", "in_progress") - agents = [ - make_task("agent-1", "pact-backend-coder: API", "in_progress"), - make_task("agent-2", "pact-frontend-coder: UI", "in_progress"), - ] - - result = build_refresh_from_tasks(feature, None, agents, []) - - assert "Active Agents (2)" in result - assert "pact-backend-coder: API" in result - assert "pact-frontend-coder: UI" in result - - def test_includes_feature_id_when_present(self, make_task): - """Test includes feature task ID in output.""" - from compaction_refresh import build_refresh_from_tasks - - feature = make_task("feat-123", "My Feature", "in_progress") - - result = build_refresh_from_tasks(feature, None, [], []) - - assert "feat-123" in result - - def test_next_step_guidance_priority(self, make_task): - """Test next step guidance prioritizes blockers over agents over phase.""" - from compaction_refresh import build_refresh_from_tasks - - feature = make_task("feat-1", "Feature", "in_progress") - phase = make_task("phase-1", "CODE: work", "in_progress") - agents = [make_task("agent-1", "pact-backend-coder: work", "in_progress")] - blockers = [make_task("block-1", "Blocker", "pending", metadata={"type": "blocker"})] - - # With blockers - should mention addressing blockers - result = build_refresh_from_tasks(feature, phase, agents, blockers) - assert "Address blockers" in result - - # Without blockers but with agents - should mention monitoring agents - result = build_refresh_from_tasks(feature, phase, agents, []) - assert "Monitor active agents" in result - - # Without blockers or agents but with phase - should mention continuing phase - result = build_refresh_from_tasks(feature, phase, [], []) - assert "Continue current phase" in result - - # With nothing - should ask user - result = build_refresh_from_tasks(feature, None, [], []) - assert "ask user how to proceed" in result.lower() - - -# ============================================================================= -# Integration Tests for Task-First Code Path -# ============================================================================= - -class TestTaskFirstIntegration: - """Integration tests for the Task-first code path in main().""" - - def test_main_uses_tasks_when_available(self, mock_tasks_dir, make_task, monkeypatch): - """Test main() uses Task system when tasks exist.""" - import json - from io import StringIO - - # Create tasks - feature = make_task("task-1", "Implement feature", "in_progress") - phase = make_task("task-2", "CODE: feature", "in_progress", blocked_by=["task-1"]) - - (mock_tasks_dir / "task-1.json").write_text(json.dumps(feature)) - (mock_tasks_dir / "task-2.json").write_text(json.dumps(phase)) - - # Simulate post-compaction input - input_data = json.dumps({"source": "compact"}) - monkeypatch.setattr("sys.stdin", StringIO(input_data)) - - # Capture output - output = StringIO() - monkeypatch.setattr("sys.stdout", output) - - from compaction_refresh import main - - with pytest.raises(SystemExit) as exc_info: - main() - - assert exc_info.value.code == 0 - - # Verify output contains Task-based refresh message - result = json.loads(output.getvalue()) - refresh_msg = result["hookSpecificOutput"]["additionalContext"] - assert "[POST-COMPACTION CHECKPOINT]" in refresh_msg - assert "Implement feature" in refresh_msg - - def test_main_skips_when_no_in_progress_tasks(self, mock_tasks_dir, make_task, monkeypatch): - """Test main() skips refresh when no tasks are in_progress.""" - import json - from io import StringIO - - # Create only completed tasks - task = make_task("task-1", "Completed work", "completed") - (mock_tasks_dir / "task-1.json").write_text(json.dumps(task)) - - input_data = json.dumps({"source": "compact"}) - monkeypatch.setattr("sys.stdin", StringIO(input_data)) - - output = StringIO() - monkeypatch.setattr("sys.stdout", output) - - from compaction_refresh import main - - with pytest.raises(SystemExit) as exc_info: - main() - - assert exc_info.value.code == 0 - # No output expected when no active workflow - assert output.getvalue() == "" diff --git a/scripts/verify-protocol-extracts.sh b/scripts/verify-protocol-extracts.sh index 69d14da..e83f5d9 100755 --- a/scripts/verify-protocol-extracts.sh +++ b/scripts/verify-protocol-extracts.sh @@ -57,15 +57,15 @@ verify "pact-s5-policy.md" "S5 Policy (lines 13-152)" "13,152" verify "pact-s4-checkpoints.md" "S4 Checkpoints (lines 154-224)" "154,224" verify "pact-s4-environment.md" "S4 Environment (lines 226-298)" "226,298" verify "pact-s4-tension.md" "S4 Tension (lines 300-363)" "300,363" -verify "pact-s1-autonomy.md" "S1 Autonomy (lines 526-599)" "526,599" -verify "pact-variety.md" "Variety (lines 644-705)" "644,705" +verify "pact-s1-autonomy.md" "S1 Autonomy (lines 517-590)" "517,590" +verify "pact-task-hierarchy.md" "Task Hierarchy (lines 635-723)" "635,723" +verify "pact-variety.md" "Variety (lines 725-786)" "725,786" # Combined-range extracts -verify "pact-s2-coordination.md" "S2 Coordination (lines 365-525 + 936-950)" "365,525" "936,950" -verify "pact-workflows.md" "Workflows (lines 706-848)" "706,848" -verify "pact-task-hierarchy.md" "Task Hierarchy (lines 860-933)" "860,933" -verify "pact-phase-transitions.md" "Phase Transitions (lines 849-859 + 951-1028)" "849,859" "951,1028" -verify "pact-documentation.md" "Documentation (lines 1029-1053)" "1029,1053" +verify "pact-s2-coordination.md" "S2 Coordination (lines 365-516 + 941-955)" "365,516" "941,955" +verify "pact-workflows.md" "Workflows (lines 787-929)" "787,929" +verify "pact-phase-transitions.md" "Phase Transitions (lines 930-940 + 956-1033)" "930,940" "956,1033" +verify "pact-documentation.md" "Documentation (lines 1034-1058)" "1034,1058" echo "" echo "=== Summary ==="