/ src / utils / human-review-queue.js
human-review-queue.js
  1  /**
  2   * Human Review Queue Manager
  3   *
  4   * Central system for tracking items that need human review:
  5   * - Architectural decisions
  6   * - Breaking changes
  7   * - Critical security issues
  8   * - Complex refactoring decisions
  9   *
 10   * Used by automated maintenance scripts to flag items for human attention.
 11   */
 12  
 13  import { run, getOne, getAll } from './db.js';
 14  
 15  /**
 16   * Initialize human review queue table
 17   */
 18  export async function initializeQueue() {
 19    await run(`
 20      CREATE TABLE IF NOT EXISTS human_review_queue (
 21        id SERIAL PRIMARY KEY,
 22        file TEXT,
 23        reason TEXT,
 24        type TEXT,
 25        priority TEXT DEFAULT 'medium',
 26        status TEXT DEFAULT 'pending',
 27        created_at TIMESTAMPTZ DEFAULT NOW(),
 28        reviewed_at TIMESTAMPTZ,
 29        reviewed_by TEXT,
 30        notes TEXT
 31      )
 32    `);
 33  }
 34  
 35  /**
 36   * Add item to review queue
 37   *
 38   * If a pending item with the same file+type already exists, it will be updated
 39   * with the new reason and timestamp (preventing duplicates).
 40   *
 41   * @param {Object} item
 42   * @param {string} item.file - File or module affected
 43   * @param {string} item.reason - Why human review is needed
 44   * @param {string} item.type - Type: 'architecture', 'breaking_change', 'security', 'documentation', 'test'
 45   * @param {string} [item.priority] - Priority: 'critical', 'high', 'medium', 'low'
 46   */
 47  export async function addReviewItem(item) {
 48    await initializeQueue();
 49  
 50    // Check if a pending item with same file+type already exists
 51    const existing = await getOne(
 52      `SELECT id FROM human_review_queue
 53       WHERE file = $1 AND type = $2 AND status = 'pending'`,
 54      [item.file, item.type]
 55    );
 56  
 57    if (existing) {
 58      // Update existing pending item with new reason and timestamp
 59      await run(
 60        `UPDATE human_review_queue
 61         SET reason = $1,
 62             priority = $2,
 63             created_at = NOW()
 64         WHERE id = $3`,
 65        [item.reason, item.priority || 'medium', existing.id]
 66      );
 67    } else {
 68      // Insert new item
 69      await run(
 70        `INSERT INTO human_review_queue (file, reason, type, priority)
 71         VALUES ($1, $2, $3, $4)`,
 72        [item.file, item.reason, item.type, item.priority || 'medium']
 73      );
 74    }
 75  }
 76  
 77  /**
 78   * Add multiple items to review queue
 79   *
 80   * Deduplicates by file+type for pending items (updates existing instead of creating duplicates).
 81   */
 82  export async function addReviewItems(items) {
 83    await initializeQueue();
 84  
 85    for (const item of items) {
 86      const existing = await getOne(
 87        `SELECT id FROM human_review_queue
 88         WHERE file = $1 AND type = $2 AND status = 'pending'`,
 89        [item.file, item.type]
 90      );
 91  
 92      if (existing) {
 93        // Update existing pending item
 94        await run(
 95          `UPDATE human_review_queue
 96           SET reason = $1,
 97               priority = $2,
 98               created_at = NOW()
 99           WHERE id = $3`,
100          [item.reason, item.priority || 'medium', existing.id]
101        );
102      } else {
103        // Insert new item
104        await run(
105          `INSERT INTO human_review_queue (file, reason, type, priority)
106           VALUES ($1, $2, $3, $4)`,
107          [item.file, item.reason, item.type, item.priority || 'medium']
108        );
109      }
110    }
111  }
112  
113  /**
114   * Get pending review items
115   */
116  export async function getPendingReviews(options = {}) {
117    const { type, priority, limit } = options;
118    await initializeQueue();
119  
120    let sql = `SELECT * FROM human_review_queue WHERE status = 'pending'`;
121    const params = [];
122    let idx = 1;
123  
124    if (type) {
125      sql += ` AND type = $${idx++}`;
126      params.push(type);
127    }
128  
129    if (priority) {
130      sql += ` AND priority = $${idx++}`;
131      params.push(priority);
132    }
133  
134    sql += ` ORDER BY
135      CASE priority
136        WHEN 'critical' THEN 1
137        WHEN 'high' THEN 2
138        WHEN 'medium' THEN 3
139        WHEN 'low' THEN 4
140      END,
141      created_at ASC`;
142  
143    if (limit) {
144      sql += ` LIMIT $${idx++}`;
145      params.push(limit);
146    }
147  
148    return await getAll(sql, params);
149  }
150  
151  /**
152   * Mark item as reviewed
153   */
154  export async function markReviewed(id, reviewedBy, notes = null) {
155    await run(
156      `UPDATE human_review_queue
157       SET status = 'reviewed',
158           reviewed_at = NOW(),
159           reviewed_by = $1,
160           notes = $2
161       WHERE id = $3`,
162      [reviewedBy, notes, id]
163    );
164  }
165  
166  /**
167   * Mark item as dismissed (not needed)
168   */
169  export async function dismissReview(id, reason = null) {
170    await run(
171      `UPDATE human_review_queue
172       SET status = 'dismissed',
173           reviewed_at = NOW(),
174           notes = $1
175       WHERE id = $2`,
176      [reason, id]
177    );
178  }
179  
180  /**
181   * Get review queue statistics
182   */
183  export async function getQueueStats() {
184    await initializeQueue();
185  
186    return await getOne(
187      `SELECT
188        COUNT(*) as total,
189        SUM(CASE WHEN status = 'pending' THEN 1 ELSE 0 END) as pending,
190        SUM(CASE WHEN status = 'reviewed' THEN 1 ELSE 0 END) as reviewed,
191        SUM(CASE WHEN priority = 'critical' AND status = 'pending' THEN 1 ELSE 0 END) as critical,
192        SUM(CASE WHEN priority = 'high' AND status = 'pending' THEN 1 ELSE 0 END) as high
193       FROM human_review_queue`
194    );
195  }
196  
197  /**
198   * Clear old reviewed items (older than 30 days)
199   */
200  export async function cleanupReviewedItems() {
201    const result = await run(
202      `DELETE FROM human_review_queue
203       WHERE status IN ('reviewed', 'dismissed')
204         AND reviewed_at < NOW() - INTERVAL '30 days'`
205    );
206    return result.changes;
207  }