IBM Bob 2.0 · core component
How IBM Bob runs inside LockSmith
LockSmith's detector and lock probe are deterministic. The part that needs judgement — turning a dangerous migration into a safe multi-step rollout and updating the application code that depends on it — is done by IBM Bob, through a custom mode and skill that ship in this repository's .bob/ folder. Bob also built most of the engine itself; every session is listed below with its real task summary.
1. Report
npm run locksmith -- --dir <repo> --json locksmith-report.json produces the findings with measured lock evidence.
2. Rewrite (Bob)
In Bob IDE, switch to 🔒 Migration Surgeon and run the lock-audit skill. Bob spawns one subagent per flagged migration and rewrites it into expand → backfill → contract steps.
3. Re-verify
Bob re-runs the LockSmith gate on its own output and writes locksmith-summary.md with the before/after score. CI blocks the merge until the gate passes.
The custom mode (.bob/custom_modes.yaml)
customModes:
- slug: migration-surgeon
name: "Migration Surgeon"
description: >-
Rewrites PostgreSQL migrations flagged by a LockSmith report into safe
expand -> backfill -> contract sequences with zero production downtime.
whenToUse: >-
Use when you have a locksmith-report.json and need to rewrite flagged
migrations into safe, ordered expand/contract files. Activate the
lock-audit skill to run the full checklist.
roleDefinition: >-
You are a PostgreSQL zero-downtime migration specialist. Your sole job is
to read a LockSmith report (locksmith-report.json) and rewrite every
flagged migration into a safe expand -> backfill -> contract sequence.
You know every lock mode PostgreSQL acquires for every DDL statement, and
you apply the safe alternative for each LockSmith rule (LS001-LS012) as
documented in .bob/rules-migration-surgeon/01-postgres-locks.md. You
understand that an ACCESS EXCLUSIVE lock that queues behind a long query
blocks every subsequent query (lock queue pile-up), so lock_timeout is
non-negotiable. You produce the minimal set of new migration files
required to make the original intent safe; you never touch migrations
that have no finding.
customInstructions: >-
RULES YOU MUST NEVER BREAK:
1. Never edit a migration file that has zero findings in the report.
2. Never weaken a rule or downgrade a severity; if LockSmith says
critical you treat it as critical.
3. Preserve migration numbering order. When a single migration must be
split, append letter suffixes: 002a_, 002b_, 002c_, etc.
4. One concern per migration file: do not combine an index creation with
a column addition in the same file.
5. Every CREATE INDEX CONCURRENTLY must be alone in its own file. The
file must start with the comment:
-- locksmith:no-transaction
This tells the CLI runner to execute the file outside a transaction
block (required for CONCURRENTLY).
6. Every migration file that issues a statement taking ShareLock or
stronger must start with:
SET lock_timeout = '3s';
Place this as the very first statement, before any DDL.
7. Data backfills must go in their own separate migration file and must
be batched by primary key range:
DO $$ DECLARE lo BIGINT; hi BIGINT; ...
LOOP ... WHERE id BETWEEN lo AND hi ... END LOOP; $$;
Never issue an un-batched UPDATE or DELETE over a whole table.
8. Column renames and drops follow strict expand/contract:
a) expand: add new column, update app source to dual-write both
columns (old and new) and read from new;
b) backfill: batch-copy old -> new where new IS NULL;
c) contract: drop old column only after the dual-write deploy is
confirmed; update app source to remove all references to old name.
9. When application source (.ts files) references a renamed or dropped
column, update those files as part of the rewrite. Only touch the
exact identifiers that changed; do not refactor surrounding code.
10. After producing all rewrite files, invoke the lock-audit skill to
re-run `npm run locksmith -- --dir <repo> --json locksmith-report.json`
and confirm: risk score dropped, no critical findings remain, gate
passes. Write the before/after summary to locksmith-summary.md.
groups:
- read
- - edit
- fileRegex: "(db/migrations/.*\\.sql|src/(?!engine/).*\\.ts|.*locksmith.*\\.md)$"
- execute
- todo
- subtask
- subagent
- skill
The skill (.bob/skills/lock-audit/SKILL.md)
---
name: lock-audit
description: >-
Use when the user wants to audit a repository's PostgreSQL migrations for
lock safety, rewrite flagged migrations into zero-downtime expand/contract
sequences, and produce a before/after LockSmith summary. Activate this skill
to run the full checklist: generate the JSON report, spawn one subagent per
flagged migration, merge results, re-run the gate, and write
locksmith-summary.md.
---
# Lock Audit Skill
Follow these steps in order. Do not skip steps. Do not begin a later step
until the previous one is complete and its output has been verified.
---
## Step 1 — Identify the target repository
Ask the user for the path to the repository to audit if it was not already
provided. Call it `<repo>` throughout these instructions.
If the user is working inside the LockSmith project itself and wants to audit
the bundled demo repo, use `./demo-repo` as `<repo>`.
---
## Step 2 — Run LockSmith and capture the baseline report
Execute the following command and wait for it to finish:
```
npm run locksmith -- --dir <repo> --json locksmith-report.json
```
Use `execute_command`. Record:
- Exit code (0 = gate passes, 1 = gate fails).
- The path to `locksmith-report.json` (written in the current working directory
unless the user specifies otherwise).
Read `locksmith-report.json` with `read_file`. Parse out:
- `summary.riskScore` per migration (before values).
- `summary.gate` (before value: "pass" or "fail").
- All `findings` entries. Group them by `migration` filename.
- Total finding count per rule ID across all migrations.
If the report contains zero findings, write a one-line `locksmith-summary.md`
stating "No findings — gate: <gate>." and stop.
---
## Step 3 — Load the lock reference
Use `use_skill` is not needed here; instead read the lock reference document
directly:
```
read_file(".bob/rules-migration-surgeon/01-postgres-locks.md")
```
Keep the full content in context for all subsequent subagent prompts. This is
the authoritative reference for safe alternatives.
---
## Step 4 — Spawn one subagent per flagged migration
For each distinct migration filename that has at least one finding:
1. Read the migration file with `read_file`.
2. Collect all findings for that migration from the report.
3. Spawn a subagent using `spawn_subagent` with `fork_context: true`.
The subagent description must contain:
- The full text of the migration file.
- The list of findings (ruleId, severity, line, message, safePattern) for
that migration only.
- The full text of `.bob/rules-migration-surgeon/01-postgres-locks.md`.
- These instructions:
"You are operating as migration-surgeon. Rewrite this migration into the
minimal set of safe SQL files following the expand/contract pattern.
Apply the safe alternative for every finding. Follow all customInstructions
from the migration-surgeon mode exactly:
- Never edit a migration that has no finding.
- Preserve numbering with letter suffixes (e.g. 002a_, 002b_).
- One concern per file.
- CREATE INDEX CONCURRENTLY in its own file starting with
'-- locksmith:no-transaction'.
- Every file touching ShareLock or stronger starts with
SET lock_timeout = '3s';
- Data backfills are batched by PK range in their own file.
- Renames/drops follow expand/contract with app source updated.
Return: a list of (filename, full SQL content) pairs for every new or
replaced file, plus a list of (filepath, diff summary) for any .ts
source changes needed."
4. Collect the subagent's returned file list.
5. Write each new migration file using `write_file` (path under `<repo>/db/migrations/`).
6. Apply any application source changes with `apply_diff` or `search_and_replace`.
Subagents for different migrations may be spawned in parallel (one `spawn_subagent`
call per migration in the same turn) since they operate on independent files.
---
## Step 5 — Re-run the gate
After all subagent writes are complete, execute:
```
npm run locksmith -- --dir <repo> --json locksmith-report-after.json
```
Read `locksmith-report-after.json`. Record:
- `summary.gate` (after value).
- `summary.riskScore` per migration (after values).
- Remaining findings grouped by ruleId.
If the gate still fails (exit code 1) or critical findings remain:
- Identify which findings were not resolved.
- For each unresolved finding, spawn a new subagent (Step 4 pattern) and repeat
Steps 4-5 until the gate passes or you have attempted three full iterations.
- After three iterations without gate passage, stop and report the remaining
findings to the user with a clear explanation of why they could not be
automatically resolved.
---
## Step 6 — Write locksmith-summary.md
Write `<repo>/locksmith-summary.md` using `write_file` with this structure:
```markdown
# LockSmith Audit Summary
**Repository:** <repo>
**Date:** <ISO date>
**Gate before:** <PASS|FAIL> **Gate after:** <PASS|FAIL>
## Risk Score by Migration
| Migration | Risk Score Before | Risk Score After | Delta |
|-----------|-------------------|------------------|-------|
| 001_... | 72 | 0 | -72 |
| ... | ... | ... | ... |
## Findings by Rule
| Rule | Severity | Count Before | Count After |
|------|----------|-------------|------------|
| LS001 | high/critical | N | 0 |
| ... | ... | N | 0 |
## Files Changed
| File | Action |
|------|--------|
| db/migrations/002a_... | created (split from 002) |
| src/orders.ts | updated (dual-write new_col) |
| ... | ... |
## Notes
<any manual steps required, e.g. application deployments between expand and contract phases>
```
---
## Step 7 — Report to the user
Provide a concise inline summary:
- Gate status changed from X -> Y.
- N migrations rewritten, M files created, P app-source files updated.
- Any remaining manual steps the developer must take (e.g. "deploy the expand
phase before running the contract migration").
- Remind the user to run the contract phase migration only after confirming
the dual-write application version is deployed and stable.
Bob task sessions used to build LockSmith
6 tasks · 12.58 Bobcoins in these task summaries (account usage 13.07 of 40, incl. one aborted prompt)Architecture and type contracts from the spec
Bob read docs/SPEC.md and the whole demo repository, then produced the module architecture, a mermaid data-flow diagram, the TypeScript contracts and a per-migration acceptance table.
Accepted. One claim in the acceptance table (LS002 on a NOT NULL column with a volatile default) was rejected in review and corrected.
docs/ARCHITECTURE.mdsrc/engine/types.ts
Static engine: splitter, classifier, 12 rules, impact model, 53 tests
Bob implemented the SQL statement splitter (comments, strings, dollar-quoting), the multi-action ALTER TABLE classifier, rules LS001–LS012, the impact estimator and analyzeRepo, then iterated on vitest until green.
Accepted after independent re-run (53/53 tests, tsc clean). Review found LS002 still over-firing; sent back to Bob in task 3.
src/engine/split.tssrc/engine/classify.tssrc/engine/rules.tssrc/engine/impact.tssrc/engine/analyze.tssrc/engine/rules.test.ts
PostgreSQL lock probe in PGlite + fix of a reviewed defect
Bob implemented the empirical probe: replay every statement inside an embedded PostgreSQL, read pg_locks for the backend, detect table rewrites via relfilenode, seed synthetic rows, and handle CONCURRENTLY outside transactions. It also fixed the LS002 over-firing found in review of task 2.
Accepted after independent re-run: 58/58 tests, tsc clean; CLI now shows measured locks (e.g. CREATE INDEX → ShareLock; ALTER TYPE → AccessExclusiveLock + rewrite).
src/engine/probe.tssrc/engine/probe.test.tssrc/engine/analyze.tssrc/engine/rules.tssrc/engine/rules.test.ts
Migration Surgeon custom mode, lock reference and lock-audit skill
Bob authored .bob/custom_modes.yaml (edit access restricted by fileRegex), a PostgreSQL lock reference it reads at rewrite time, the lock-audit skill checklist and a developer guide. It self-tested the fileRegex and tightened it so the mode cannot edit LockSmith's own engine.
Accepted. YAML validated independently; Bob IDE picked the mode up immediately (visible in the mode picker).
.bob/custom_modes.yaml.bob/rules-migration-surgeon/01-postgres-locks.md.bob/skills/lock-audit/SKILL.mddocs/BOB_MODE.md
Bob rewrites the release: 6 parallel subagents, gate FAIL → PASS
In Migration Surgeon mode Bob ran the lock-audit skill on a working copy of the demo repo: it ran the gate, spawned one subagent per flagged migration in parallel, replaced 6 dangerous migrations with 13 ordered safe files, updated app code for dual-writes, re-ran the gate, and withdrew its own column-drop step when it recognised the app still used the column.
Accepted after independent gate run: FAIL (risk 100) → PASS (risk 40). The remaining finding was an engine false positive, fixed in task 6.
demo-repo-safe/db/migrations/*demo-repo-safe/src/customers.tsdemo-repo-safe/src/orders.tsdemo-repo-safe/locksmith-summary.md
Debugging an engine false positive + security review of the public API
Bob taught LS005 the PostgreSQL 12+ pattern (validated CHECK (col IS NOT NULL) makes SET NOT NULL scan-free) with regression tests, and reviewed /api/analyze, fixing four issues: unbounded body before JSON parse, unbounded tableStats loop, array bypass of the object guard, and internal error leakage.
Accepted after independent re-run: 61/61 tests, tsc clean; Bob-rewritten repo now scores risk 0 with 0 findings, original still FAIL/100.
src/engine/rules.tssrc/engine/rules.test.tssrc/app/api/analyze/route.ts