All skills
asyrafhussin avatar

/technical-debt

@4df703d

Technical debt inventory, prioritization, and audit for PHP/Laravel (MySQL) and Node/TypeScript/React projects. Use when assessing code health, identifying refactoring candidates, planning debt paydown, or auditing a codebase for accumulated debt. Triggers on "audit technical debt", "find tech debt", "debt inventory", "what should we refactor first", or tasks involving code health, security debt, performance debt, data debt, observability debt, debt prioritization, or remediation planning.

Use this Skill: https://skilld.dev/gh/asyrafhussin/agent-skills/technical-debt

This session only. Nothing lands on disk.

rulesdata-orphaned-records.md

≈1.4k tokens on demand. Your agent reads this file only when SKILL.md points to it.

Orphaned Records and Referential Drift

Impact: MEDIUM (Bugs, broken reports, and inconsistent state compound over time)

Orphaned records — child rows whose parent no longer exists — are usually invisible until a report breaks or a query joins fail mysteriously. The root cause is almost always missing foreign-key constraints, ad-hoc deletes that bypass the ORM, or "soft-delete the parent, leave children active" semantics.

How to Detect

-- Orphans on a single relation
SELECT child.id, child.parent_id
FROM order_items child
LEFT JOIN orders parent ON parent.id = child.parent_id
WHERE parent.id IS NULL;

-- MySQL: columns ending in _id that are NOT part of any FK constraint.
-- Note: `_` is a single-char wildcard in LIKE — escape it (use `#` as ESCAPE char to
-- avoid backslash-escape confusion in MySQL string literals). Exclude the PK `id` column.
SELECT c.TABLE_NAME, c.COLUMN_NAME
FROM information_schema.COLUMNS c
WHERE c.TABLE_SCHEMA = DATABASE()
  AND c.COLUMN_NAME LIKE '%#_id' ESCAPE '#'
  AND c.COLUMN_NAME <> 'id'
  AND NOT EXISTS (
    SELECT 1
    FROM information_schema.KEY_COLUMN_USAGE kcu
    JOIN information_schema.TABLE_CONSTRAINTS tc
      ON tc.CONSTRAINT_NAME   = kcu.CONSTRAINT_NAME
     AND tc.CONSTRAINT_SCHEMA = kcu.CONSTRAINT_SCHEMA
     AND tc.TABLE_NAME        = kcu.TABLE_NAME
    WHERE tc.CONSTRAINT_TYPE   = 'FOREIGN KEY'
      AND kcu.TABLE_SCHEMA     = c.TABLE_SCHEMA
      AND kcu.TABLE_NAME       = c.TABLE_NAME
      AND kcu.COLUMN_NAME      = c.COLUMN_NAME
  );

-- Soft-deleted parents with active children
SELECT COUNT(*) FROM orders
WHERE deleted_at IS NOT NULL
  AND EXISTS (
    SELECT 1 FROM order_items
    WHERE order_id = orders.id AND deleted_at IS NULL
  );

Incorrect

// ❌ No FK constraints; manual delete bypasses cascades
Schema::create('order_items', function (Blueprint $t) {
    $t->id();
    $t->unsignedBigInteger('order_id');          // no constraint, no index
    $t->unsignedBigInteger('product_id');
});

// Deletion via raw SQL — children become orphans
DB::table('orders')->where('status', 'cancelled')->delete();
// 200 order rows gone; their 1,800 order_items now point to nothing.

// Soft-delete parent, hard-active children — reports show ghost orders
class Order extends Model { use SoftDeletes; }
class OrderItem extends Model {}   // does NOT respect parent's deleted_at

Problems:

  • Joins return rows but with NULL parents, silently breaking sums and counts
  • Cleanup jobs run forever ("delete order_items with no order" finds millions)
  • Restore-from-backup workflows can't trust the data they restore

Correct

// ✅ FK constraints with explicit cascade behaviour
Schema::create('order_items', function (Blueprint $t) {
    $t->id();
    $t->foreignId('order_id')
        ->constrained()                     // creates FK to orders(id)
        ->cascadeOnDelete();                // delete items when order is deleted
    $t->foreignId('product_id')
        ->constrained()
        ->restrictOnDelete();               // can't delete a product still referenced
});

// ✅ Soft-deletes coordinated across parent and children
//    Both models must use SoftDeletes for soft-cascade to work (otherwise
//    `$order->items()->delete()` will hard-delete the children).
class OrderItem extends Model {
    use SoftDeletes;
}

class Order extends Model {
    use SoftDeletes;
    protected static function booted() {
        static::deleted(fn($order) => $order->items()->delete());      // soft-deletes children
        static::restored(fn($order) => $order->items()->withTrashed()->restore());
    }
}
-- ✅ Add missing FKs to legacy tables (MySQL/InnoDB; clean orphans first)
-- Note: adding a FK with `ALGORITHM=INPLACE` is only supported when
-- `foreign_key_checks=OFF`, and `LOCK=NONE` is NOT supported for FK adds.
-- With default settings MySQL forces `ALGORITHM=COPY`, which rewrites
-- the table. For large tables, use `pt-online-schema-change` or `gh-ost`
-- to add the FK without blocking writes.
ALTER TABLE order_items
  ADD CONSTRAINT fk_order_items_order
  FOREIGN KEY (order_id) REFERENCES orders(id) ON DELETE CASCADE;

Benefits:

  • Database enforces integrity even when application code is buggy
  • Reports and joins are trustworthy
  • Cleanup jobs become unnecessary (or trivial)

Remediation Strategy

  • Effort:
    • S — adding constraints to greenfield tables
    • M — backfilling constraints + cleanup on small/medium tables
    • L — adding constraints to a 100M+ row table (must clean orphans first, then add constraint online)
  • When to pay down:
    • NOW: any data integrity issue traced back to orphans
    • As cleanup project: audit tables for _id columns without FK constraints
    • Then: make FKs required for all new tables (lint a migration template)

Cleanup workflow:

  1. Run the orphan-detection query above for each suspected relation
  2. Decide policy: hard-delete orphans, mark them archived, or attach to a placeholder parent
  3. Apply the cleanup in batches (avoid one giant transaction)
  4. Add the FK constraint
  5. Add a CI test that runs the orphan-detection query against the test DB after each test run

Reference: MySQL — FOREIGN KEY Constraints · Laravel — Foreign Key Constraints

Source: SKILL.md on GitHub

No alerts16d3 checks · Risk SAFE
  • Gen Agent Trust Hub16d

    The skill provides a comprehensive and safe framework for identifying, prioritizing, and auditing technical debt in PHP/Laravel and Node/TypeScript/React projects. No security risks or malicious behaviors were detected.

  • Socket16d

    No alerts

  • Snyk16d

    Risk: LOW · No issues

Signed by skilld at 4df703d. This ties the file your Agent reads to that commit on GitHub. It does not review the instructions.

Last checked against GitHub last month.

Steadyupdated 5 months ago
metadata
{
  "author": "agent-skills",
  "version": "1.0.0"
}

README badge

README badge for asyrafhussin/agent-skills/technical-debt