Inline # comments in gitignore are not supported — they were silently breaking three patterns (data/checkpoints/, data/adapter-migration-state.json, .claude/agents/.generated/). Moved comments to their own lines and added missing entries for runtime dirs (data/audit/, data/logs/, etc.) and temp files (.interaction_tmp.json, .design-build/, .taskmaster bak files). Also tracks previously untracked legitimate files: scripts, tests, docs, skills references, .env.example, taskmaster templates. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
349 lines
10 KiB
Markdown
349 lines
10 KiB
Markdown
# Hooks: Case Status Webhooks Implementation Plan
|
||
|
||
> **For agentic workers:** REQUIRED SUB-SKILL: Use superpowers:subagent-driven-development (recommended) or superpowers:executing-plans to implement this plan task-by-task. Steps use checkbox (`- [ ]`) syntax for tracking.
|
||
|
||
**Goal:** כאשר status של תיק משתנה ב-legal-ai (e.g. `qa_failed`, `exported`), ה-plugin מקבל webhook, מעדכן את ה-issue ב-Paperclip, ומעיר את ה-CEO במקרה הצורך.
|
||
|
||
**Architecture:** Legal-ai REST API קורא `pc_request("POST", "/api/plugins/marcusgroup.legal-ai/webhooks/case-status", ...)` אחרי כל שינוי status. ה-plugin מטפל ב-`onWebhook()` ומגיב: מוסיף תגובה לissue, מעיר CEO אם QA נכשל.
|
||
|
||
**Tech Stack:** TypeScript (plugin-legal-ai), Python/FastAPI (legal-ai web), `@paperclipai/plugin-sdk@2026.325.0`, `httpx` (Python), `pc_request` helper.
|
||
|
||
---
|
||
|
||
## File Map
|
||
|
||
| Action | File |
|
||
|--------|------|
|
||
| Modify | `plugin-legal-ai/src/worker.ts` — add `onWebhook()` to `definePlugin({})` |
|
||
| Modify | `plugin-legal-ai/plugin.json` — add `"webhooks.receive"` capability |
|
||
| Modify | `plugin-legal-ai/src/manifest.ts` — add webhook capability |
|
||
| Modify | `legal-ai/web/app.py` — emit webhook after `PUT /api/cases/{case_number}` |
|
||
| Modify | `legal-ai/web/paperclip_api.py` — add `emit_webhook()` helper |
|
||
|
||
---
|
||
|
||
## Task 1: Add `emit_webhook` helper ב-Python
|
||
|
||
**Files:**
|
||
- Modify: `legal-ai/web/paperclip_api.py`
|
||
|
||
- [ ] **Step 1: קרא את הקובץ הקיים**
|
||
|
||
```bash
|
||
head -90 /home/chaim/legal-ai/web/paperclip_api.py
|
||
```
|
||
|
||
- [ ] **Step 2: הוסף את ה-helper בסוף הקובץ**
|
||
|
||
פתח `/home/chaim/legal-ai/web/paperclip_api.py` והוסף אחרי הפונקציה `pc_request`:
|
||
|
||
```python
|
||
async def emit_case_status_webhook(
|
||
case_number: str,
|
||
old_status: str,
|
||
new_status: str,
|
||
company_id: str | None = None,
|
||
run_id: str | None = None,
|
||
) -> None:
|
||
"""Notify the Paperclip plugin that a case status changed.
|
||
|
||
Fire-and-forget: logs errors but never raises, so callers aren't blocked.
|
||
"""
|
||
try:
|
||
await pc_request(
|
||
"POST",
|
||
"/api/plugins/marcusgroup.legal-ai/webhooks/case-status",
|
||
json={
|
||
"caseNumber": case_number,
|
||
"oldStatus": old_status,
|
||
"newStatus": new_status,
|
||
"companyId": company_id,
|
||
"timestamp": datetime.utcnow().isoformat() + "Z",
|
||
},
|
||
run_id=run_id,
|
||
timeout=5.0,
|
||
)
|
||
except Exception as exc:
|
||
logger.warning("emit_case_status_webhook failed: %s", exc)
|
||
```
|
||
|
||
> **הערה:** `datetime` ו-`logger` כבר מיובאים ב-`app.py`. בדוק שהם מיובאים גם ב-`paperclip_api.py` — אם לא, הוסף `from datetime import datetime` ו-`import logging; logger = logging.getLogger(__name__)` בראש הקובץ.
|
||
|
||
- [ ] **Step 3: Commit**
|
||
|
||
```bash
|
||
cd /home/chaim/legal-ai
|
||
git add web/paperclip_api.py
|
||
git commit -m "feat: add emit_case_status_webhook helper"
|
||
```
|
||
|
||
---
|
||
|
||
## Task 2: צרף webhook לendpoint `PUT /api/cases/{case_number}`
|
||
|
||
**Files:**
|
||
- Modify: `legal-ai/web/app.py`
|
||
|
||
- [ ] **Step 1: מצא את ה-endpoint**
|
||
|
||
```bash
|
||
grep -n "PUT\|case_number\|update_case" /home/chaim/legal-ai/web/app.py | head -30
|
||
```
|
||
|
||
- [ ] **Step 2: קרא את הenable endpoint המלא**
|
||
|
||
זהה את הסקציה המלאה של ה-endpoint ואת הייבוא הקיים.
|
||
|
||
- [ ] **Step 3: הוסף import ל-emit_webhook**
|
||
|
||
בראש `app.py`, בסקציית ה-imports מ-`paperclip_api`:
|
||
```python
|
||
from .paperclip_api import pc_request, emit_case_status_webhook
|
||
```
|
||
|
||
- [ ] **Step 4: הוסף webhook emit בתוך הendpoint**
|
||
|
||
אחרי שהקוד מעדכן את ה-case (לפני ה-`return`), הוסף:
|
||
|
||
```python
|
||
# Notify plugin about status change (fire-and-forget)
|
||
if updates.get("status") and old_status != updates["status"]:
|
||
background_tasks.add_task(
|
||
emit_case_status_webhook,
|
||
case_number=case_number,
|
||
old_status=old_status,
|
||
new_status=updates["status"],
|
||
company_id=str(case.get("company_id")),
|
||
)
|
||
```
|
||
|
||
> אם ה-endpoint כבר מקבל `background_tasks: BackgroundTasks` — השתמש בו. אם לא, הוסף `background_tasks: BackgroundTasks` לחתימת הפונקציה. הוסף `from fastapi import BackgroundTasks` ל-imports.
|
||
|
||
- [ ] **Step 5: שמור את ה-`old_status` לפני ה-update**
|
||
|
||
בתחילת ה-endpoint handler, לפני קריאת ה-DB update:
|
||
```python
|
||
old_status = (await db.get_case(case_number) or {}).get("status", "")
|
||
```
|
||
|
||
- [ ] **Step 6: בדיקה בסיסית — שלח PUT ידני**
|
||
|
||
```bash
|
||
curl -s -X PUT https://legal-ai.nautilus.marcusgroup.org/api/cases/1130-25 \
|
||
-H "Content-Type: application/json" \
|
||
-d '{"status": "in_progress"}' | jq .status
|
||
```
|
||
|
||
בדוק ב-Paperclip logs שהwebhook נשלח (עדיין לא מטופל בצד ה-plugin):
|
||
```bash
|
||
pm2 logs paperclip --lines 20
|
||
```
|
||
|
||
- [ ] **Step 7: Commit**
|
||
|
||
```bash
|
||
cd /home/chaim/legal-ai
|
||
git add web/app.py
|
||
git commit -m "feat: emit case-status webhook on PUT /api/cases/:case"
|
||
```
|
||
|
||
---
|
||
|
||
## Task 3: הוסף `onWebhook()` ל-plugin
|
||
|
||
**Files:**
|
||
- Modify: `plugin-legal-ai/src/worker.ts`
|
||
|
||
- [ ] **Step 1: קרא את `definePlugin({})` הקיים**
|
||
|
||
```bash
|
||
grep -n "definePlugin\|onWebhook\|onHealth\|onShutdown" /home/chaim/plugin-legal-ai/src/worker.ts
|
||
```
|
||
|
||
- [ ] **Step 2: הוסף את `onWebhook` handler**
|
||
|
||
בתוך הobject שמועבר ל-`definePlugin({})`, אחרי `setup(ctx)`:
|
||
|
||
```typescript
|
||
onWebhook: async (input) => {
|
||
const { endpointKey, payload, companyId } = input as {
|
||
endpointKey: string;
|
||
payload: {
|
||
caseNumber: string;
|
||
oldStatus: string;
|
||
newStatus: string;
|
||
companyId: string;
|
||
timestamp: string;
|
||
};
|
||
companyId: string;
|
||
};
|
||
|
||
if (endpointKey !== "case-status") return;
|
||
|
||
const { caseNumber, oldStatus, newStatus } = payload;
|
||
ctx.logger.info(`Webhook: ${caseNumber} ${oldStatus} → ${newStatus}`);
|
||
|
||
// Find the Paperclip issue linked to this case
|
||
const stateKey = `case:${caseNumber}`;
|
||
const issueId = await ctx.state.get({ companyId }, stateKey);
|
||
if (!issueId) {
|
||
ctx.logger.warn(`No issue found for case ${caseNumber}`);
|
||
return;
|
||
}
|
||
|
||
const statusLabels: Record<string, string> = {
|
||
in_progress: "🔄 בעבודה",
|
||
drafted: "✍️ טיוטה מוכנה",
|
||
qa_failed: "❌ QA נכשל",
|
||
exported: "📄 יוצא ל-DOCX",
|
||
reviewed: "✅ נבדק",
|
||
final: "🎯 סופי",
|
||
};
|
||
|
||
const label = statusLabels[newStatus] ?? newStatus;
|
||
|
||
// Post a status comment on the issue
|
||
await ctx.issues.createComment({
|
||
issueId: issueId as string,
|
||
body: `**עדכון סטטוס:** ${label} (היה: ${oldStatus})`,
|
||
});
|
||
|
||
// Wake CEO if QA failed
|
||
if (newStatus === "qa_failed") {
|
||
const companies = await ctx.companies.list();
|
||
const company = companies.find((c) => c.id === companyId);
|
||
if (!company) return;
|
||
|
||
const CEO_IDS: Record<string, string> = {
|
||
"42a7acd0-30c5-4cbd-ac97-7424f65df294": "752cebdd-6748-4a04-aacd-c7ab0294ef33",
|
||
"8639e837-4c9d-47fa-a76b-95788d651896": "cdbfa8bc-3d61-41a4-a2e7-677ec7d34562",
|
||
};
|
||
const ceoId = CEO_IDS[companyId];
|
||
if (ceoId) {
|
||
await ctx.agents.invoke(ceoId, companyId, {
|
||
prompt: `תיק ${caseNumber} נכשל בבדיקת QA. עיין בתוצאות QA ותקן את הבעיות.`,
|
||
reason: "qa_failed webhook",
|
||
});
|
||
}
|
||
}
|
||
},
|
||
```
|
||
|
||
> **הערה:** `ctx` חייב להיות נגיש ב-`onWebhook`. אם `ctx` מוגדר בתוך `setup()` בלבד — הוצא אותו ל-closure חיצוני של ה-plugin object (ראה את הדפוס הקיים ב-`worker.ts`).
|
||
|
||
- [ ] **Step 3: בדק TypeScript**
|
||
|
||
```bash
|
||
cd /home/chaim/plugin-legal-ai && npx tsc --noEmit
|
||
```
|
||
|
||
Expected: 0 errors.
|
||
|
||
- [ ] **Step 4: Build**
|
||
|
||
```bash
|
||
cd /home/chaim/plugin-legal-ai && npm run build
|
||
```
|
||
|
||
Expected: `dist/worker.js` נוצר ללא שגיאות.
|
||
|
||
- [ ] **Step 5: Commit**
|
||
|
||
```bash
|
||
cd /home/chaim/plugin-legal-ai
|
||
git add src/worker.ts
|
||
git commit -m "feat: add onWebhook handler for case-status events"
|
||
```
|
||
|
||
---
|
||
|
||
## Task 4: הוסף capability ל-`plugin.json` ול-`manifest.ts`
|
||
|
||
**Files:**
|
||
- Modify: `plugin-legal-ai/plugin.json`
|
||
- Modify: `plugin-legal-ai/src/manifest.ts`
|
||
|
||
- [ ] **Step 1: הוסף `"webhooks.receive"` ל-capabilities**
|
||
|
||
ב-`plugin-legal-ai/plugin.json`, בarray `"capabilities"`, הוסף:
|
||
```json
|
||
"webhooks.receive"
|
||
```
|
||
|
||
- [ ] **Step 2: הוסף גם ב-`manifest.ts`**
|
||
|
||
```bash
|
||
grep -n "capabilities\|webhooks" /home/chaim/plugin-legal-ai/src/manifest.ts
|
||
```
|
||
|
||
הוסף `"webhooks.receive"` לarray שם.
|
||
|
||
- [ ] **Step 3: Re-install plugin**
|
||
|
||
```bash
|
||
cd /home/chaim/plugin-legal-ai && npm run build
|
||
npx paperclipai plugin uninstall marcusgroup.legal-ai \
|
||
--api-base http://localhost:3100 --api-key pcapi_legal_install_key_2026
|
||
npx paperclipai plugin install /home/chaim/plugin-legal-ai \
|
||
--api-base http://localhost:3100 --api-key pcapi_legal_install_key_2026
|
||
pm2 restart paperclip
|
||
```
|
||
|
||
- [ ] **Step 4: Commit**
|
||
|
||
```bash
|
||
cd /home/chaim/plugin-legal-ai
|
||
git add plugin.json src/manifest.ts
|
||
git commit -m "feat: add webhooks.receive capability to plugin manifest"
|
||
```
|
||
|
||
---
|
||
|
||
## Task 5: בדיקה end-to-end
|
||
|
||
- [ ] **Step 1: Deploy legal-ai**
|
||
|
||
```bash
|
||
cd /home/chaim/legal-ai
|
||
git push origin main
|
||
# המתן לבנייה (~2-4 דקות)
|
||
```
|
||
|
||
- [ ] **Step 2: שנה סטטוס תיק**
|
||
|
||
```bash
|
||
curl -s -X PUT https://legal-ai.nautilus.marcusgroup.org/api/cases/1130-25 \
|
||
-H "Content-Type: application/json" \
|
||
-d '{"status": "qa_failed"}' | jq .status
|
||
```
|
||
|
||
- [ ] **Step 3: בדוק שהתגובה הוספה ל-Paperclip issue**
|
||
|
||
```bash
|
||
# מצא את ה-issue הקשור לתיק 1130-25
|
||
pm2 logs paperclip --lines 30 | grep "1130-25\|webhook\|qa_failed"
|
||
```
|
||
|
||
Expected: תגובה "❌ QA נכשל" הוספה לissue. CEO הועיר.
|
||
|
||
- [ ] **Step 4: בדוק שינוי סטטוס שגרתי (לא QA)**
|
||
|
||
```bash
|
||
curl -s -X PUT https://legal-ai.nautilus.marcusgroup.org/api/cases/1130-25 \
|
||
-H "Content-Type: application/json" \
|
||
-d '{"status": "drafted"}' | jq .status
|
||
```
|
||
|
||
Expected: תגובה "✍️ טיוטה מוכנה" בissue. CEO **לא** הועיר.
|
||
|
||
---
|
||
|
||
## אימות סופי
|
||
|
||
| בדיקה | פקודה | תוצאה מצופה |
|
||
|-------|-------|-------------|
|
||
| QA נכשל → CEO מועיר | `PUT status=qa_failed` | תגובה + agent invocation |
|
||
| exported → תגובה בלבד | `PUT status=exported` | תגובה בלבד |
|
||
| שינוי ללא status | `PUT title=...` | שום webhook |
|
||
| תיק ללא issue | webhook לתיק חדש | לוג warning, ללא crash |
|