Skip to content

feat(#59): ticket-first gate for database migrations + /migration skill + migration AgDR template#72

Merged
atlas-apex merged 2 commits into
mainfrom
feature/#59-migration-gate
Apr 17, 2026
Merged

feat(#59): ticket-first gate for database migrations + /migration skill + migration AgDR template#72
atlas-apex merged 2 commits into
mainfrom
feature/#59-migration-gate

Conversation

@atlas-apex

Copy link
Copy Markdown
Collaborator

Summary

Ships Phase 1 of the migration-gate feature: a new hook that refuses edits to migration files without a labelled migration ticket + linked AgDR, plus the /migration skill that produces both artefacts in one guided flow, plus the migration AgDR template.

Three layers:

  1. Hook (.claude/hooks/require-migration-ticket.sh) — PreToolUse on Edit/Write/MultiEdit. On migration-path match, runs three gates: active ticket marker exists, referenced issue is OPEN with migration label, issue body references a migration AgDR. Runs before require-active-ticket.sh so migration-specific messages surface first.

  2. Skill (.claude/skills/migration/SKILL.md) — asks for type / affected tables / rollback plan / downtime / consumers / volume / testing / observability. Writes the AgDR first (reversible local write), then creates the labelled GitHub issue, then back-fills the AgDR with the ticket URL. Tells the user to run /start-ticket as the next step — doesn't do it automatically so the handoff stays explicit.

  3. Template (templates/agdr-migration.md) — extends the base AgDR shape with migration-specific sections: explicit Rollback Plan (not buried under Consequences), Cross-Service Consumers, Testing Plan, Observability, plus frontmatter fields for migration type / affected tables / downtime / data volume / target environments.

Default migration paths (overridable per project via .claude/project-config.jsonmigration_paths)

  • **/migrate-*.{ts,js,py,sql} — one-off migration scripts
  • **/migrations/** — any file under a migrations/ directory
  • prisma/schema.prisma, prisma/migrations/** — Prisma
  • src/migrations/*.{ts,js} — TypeORM convention
  • alembic/versions/*.py — Alembic
  • db/migrate/*.rb — Rails
  • *.sql under any migrations/ subtree

Exempt regardless of pattern: .claude/, docs/, projects/*/docs/, any *.md, any *.example.

Docs wiring

  • .claude/rules/workflow-gates.md: new gate row 3a + dedicated "Migration Gate" section
  • .claude/hooks/README.md: new section 1a
  • CLAUDE.md: hook count 16→17, skill count 31→32, template + skill table rows
  • workflows/sdlc.md: new "Sub-Workflow: Database Migrations" block after Phase 3 showing /migration/start-ticket → edit → dev smoke → staging → prod → monitor

Testing

11 path-matching smoke cases against an isolated /tmp mock ops root — all pass:

PASS  non-migration *.ts → pass-through
PASS  *.md → pass-through
PASS  .claude/ → pass-through
PASS  *.example → pass-through
PASS  migrate-*.ts, no marker → BLOCK
PASS  migrations/NNN.sql, no marker → BLOCK
PASS  prisma/schema.prisma, no marker → BLOCK
PASS  src/migrations/*.ts, no marker → BLOCK
PASS  alembic/versions/*.py, no marker → BLOCK
PASS  db/migrate/*.rb, no marker → BLOCK
PASS  any file under migrations/, no marker → BLOCK

Gates 2 (label check) and 3 (AgDR body ref) require live gh calls — verified in real-usage flow: /migration → create ticket + AgDR → /start-ticket → retry the migration-file edit → hook allows.

Deferred — issue's Phase 2

sam validate / terraform plan introspection to auto-detect AWS::DynamoDB::Table / aws_rds_cluster / aws_dynamodb_table additions-or-modifications. Complex, and 80% of the value comes from the path-pattern heuristic + per-project migration_paths overrides.

Glossary

Term Definition
migration gate The three-check PreToolUse hook that refuses migration-file edits without a labelled ticket + AgDR
migration path A file path matching one of the default patterns (or a project-configured pattern) that identifies a database-migration file
migration AgDR An Agent Decision Record that captures rollback plan, downtime, cross-service consumers, testing plan, and observability for a migration — lives at docs/agdr/AgDR-NNNN-migration-<slug>.md in the project's repo
rollback runbook The explicit step-by-step sequence to reverse a migration, written at AgDR time (not post-hoc during review)

Related

…ll + migration AgDR template

Ships Phase 1 of the migration-gate feature per the issue's own
rollout plan. Phase 2 (sam/terraform plan introspection) is deferred —
path-pattern heuristics cover 80% of the real cases and the remaining
20% can be handled by per-project `migration_paths` overrides.

What ships:

1. `.claude/hooks/require-migration-ticket.sh` — new PreToolUse hook on
   Edit/Write/MultiEdit. If the target path matches a migration
   pattern, runs three gates:
     G1: active ticket marker exists (same #41 two-tier resolution —
         per-project preferred, ops-level fallback)
     G2: the referenced GitHub issue is OPEN and carries the
         `migration` label (default, overridable via
         `.claude/project-config.json` → `migration_label`)
     G3: the issue body contains a reference to a migration AgDR
         matching `docs/agdr/AgDR-\d+-.*migration.*\.md`
   Non-migration paths exit silently; the normal require-active-ticket
   hook then applies. Exempts .claude/, docs/, projects/*/docs/,
   *.md, and *.example up front.

2. `.claude/skills/migration/SKILL.md` — guided flow that produces
   both artefacts in one pass. Asks for migration type, affected
   tables, rollback plan (required non-empty), downtime estimate,
   cross-service consumers, data volume, testing plan, and
   observability. Writes the AgDR first (reversible local write),
   then creates the labelled GitHub issue, then back-fills the AgDR
   with the issue reference. Tells the user to run /start-ticket as
   the next step — doesn't do it automatically so the handoff stays
   explicit.

3. `templates/agdr-migration.md` — AgDR template with migration-
   specific sections: Rollback Plan (explicit, not buried under
   Consequences), Cross-Service Consumers, Testing Plan, and
   Observability. Built on the same shape as the base AgDR template
   (Context / Options Considered / Decision) so existing AgDR
   discipline carries over.

Wiring:

- `.claude/settings.json`: require-migration-ticket.sh registered
  BEFORE require-active-ticket.sh in the PreToolUse chain so
  migration-specific blocking messages surface first.
- `.claude/rules/workflow-gates.md`: new gate 3a row + dedicated
  "Migration Gate" section with default path list and enforcement
  note.
- `.claude/hooks/README.md`: new section 1a documenting the hook's
  three gates. Hook #1 (require-active-ticket) description updated
  to include the #41 two-tier resolution.
- `CLAUDE.md`: hook count 16 → 17, skill count 31 → 32, templates
  table gains Migration AgDR row, skills table gains /migration row.
- `workflows/sdlc.md`: new "Sub-Workflow: Database Migrations"
  section after Phase 3 Exit Criteria, showing the flow from
  /migration → /start-ticket → edit → dev smoke → staging →
  prod → monitor.

Smoke-tested the hook with 11 path-matching cases in an isolated
/tmp mock ops root (/tmp/smoke-59.sh). All pass — non-migration
paths pass through, every migration-pattern path blocks when no
marker exists. Gates 2 and 3 require live gh calls and are covered
by real-usage flow (documented in the test output).

Closes #59

@atlas-apex atlas-apex left a comment

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Code Review: PR #72

Commit: e8ca0372b62b6db483f685ca3e583d35f2e7ccfd

Summary

Phase 1 of the migration gate ships cleanly: new require-migration-ticket.sh PreToolUse hook (3 gates: marker → open+labelled issue → AgDR body ref), /migration skill that produces ticket + AgDR in one guided flow, and templates/agdr-migration.md extending base AgDR with rollback/consumers/testing/observability. Docs wiring is consistent (hook count 16→17, skill count 31→32, new gate row 3a, new SDLC sub-workflow block).

Checklist Results

  • Architecture & Design: Pass — hook slots in BEFORE active-ticket, reuses #41 marker resolution cleanly, skill writes AgDR first (reversible) then ticket then back-fill.
  • Code Quality: Pass — is_migration_path() isolated, exemptions short-circuit before network, clear block messages point at /migration with exact invocation.
  • Testing: Pass for path-matching (11/11 smoke cases); Gates 2/3 verified via real-usage flow (noted in PR body).
  • Security: N/A — no new surface; gh auth reused, no shell-injection (all interpolations quoted).
  • Performance: Pass — exempt paths exit before any gh call.
  • PR Description & Glossary: Pass — 4 terms, all migration-specific.
  • Technical Decisions (AgDR): N/A — this PR IS the AgDR mechanism; no separate decision record needed for shipping the gate itself.

Findings

  1. Path-match false-positive risk — LOW, but one sharp edge. The generic fallback */migrations/* (line 156) is the widest pattern. It catches config/migrations/notes.md (though .md exempt short-circuits earlier) and would catch e.g. src/utils/migrations/helpers.ts if a project puts non-migration helpers under a migrations/ subtree. Acceptable given exemptions cover docs/meta and projects can override via migration_paths. The specific concern raised (migrations.yml in a config dir): safe, because */migrations/* requires /migrations/ as a directory segment, not a filename prefix.

  2. Custom-paths override semantic (override, not union) — CORRECT. Lines 129–139 explicitly bail after custom patterns without falling through. This is right: a project setting migration_paths is declaring "here, and nowhere else, are my migrations." Union semantics would surprise — a user who just registered src/db/** would not expect db/migrate/*.rb to also gate. Consider documenting this explicitly in hooks/README.md (the skill already implies it).

  3. Gate ordering UX — CORRECT. Migration hook before active-ticket means migration files get the targeted message (pointing at /migration), non-migration files get the generic active-ticket message. Pass-through when not a migration path is silent (exit 0) so no double-block.

  4. AgDR slug regex — SUFFICIENTLY STRICT. AgDR-[0-9]+-[^[:space:]]*migration[^[:space:]]*\.md requires the word "migration" in the slug. Technically a user could game it by naming a non-migration AgDR with "migration" in the slug — but that's active evasion, not accidental bypass. Combined with the template forcing the shape, this is the right strictness level. Tightening to AgDR-\d+-migration- would be more rigid but would break the handful of legit slugs like AgDR-0042-data-migration-split.md.

  5. Skill flow (AgDR-first, ticket-second, back-fill-third) — CLEAR. Rationale in rule 5 is well-articulated (ghost-ticket exposure minimised). The explicit "do NOT auto-run /start-ticket" handoff is the right UX call.

Suggestions (non-blocking)

  • Consider logging the matched pattern in the block message (already done on gate 1 block — line 198 — nice touch).
  • hooks/README.md could note the "override replaces, not unions" semantic explicitly.

Verdict

APPROVED


Rex (Code Reviewer Agent)
Reviewed commit: e8ca0372b62b6db483f685ca3e583d35f2e7ccfd

- require-migration-ticket.sh: remove subsumed case patterns (*/projects/*/docs/* under */docs/*, */migrations/*/*.sql under */migrations/*.sql) and disable SC2254 on the intentional glob-expansion in the custom-paths case.
- agdr-migration.md: add blank line before a bulleted list under 'Deploy-order constraint'.

All 11 smoke cases still pass.

@atlas-apex atlas-apex left a comment

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Code Review: PR #72 (re-review)

Commit: a2694e93239812938df18b25f20a1b2b419e0da0

Summary

Incremental follow-up addressing 4 CI-lint findings. Verified diff vs prior approved SHA e8ca037 is purely lint fixes — no logic changes.

Incremental Diff Verified

  • .claude/hooks/require-migration-ticket.sh:
    • Exempt-arm: removed subsumed */projects/*/docs/* (covered by */docs/* since shell case * crosses /), added explanatory comment — correct.
    • Default migration-arm: removed subsumed */migrations/*/*.sql (covered by */migrations/*.sql), added explanatory comment — correct.
    • Added # shellcheck disable=SC2254 before the intentional glob case "$path" in $pat) with clear justification — correct.
  • templates/agdr-migration.md: added blank line before bulleted list (MD032) — correct.

Checklist Results

  • Architecture & Design: Pass (unchanged)
  • Code Quality: Pass
  • Testing: Pass (unchanged)
  • Security: Pass (unchanged)
  • Performance: Pass (unchanged)
  • PR Description & Glossary: Pass (unchanged)
  • Technical Decisions (AgDR):Pass (unchanged)

Verdict

APPROVED (via comment — cannot self-approve own PR) — lint-only delta, semantics preserved, CI green.


Reviewed by Rex (Code Reviewer Agent)
Reviewed commit: a2694e93239812938df18b25f20a1b2b419e0da0

@atlas-apex atlas-apex merged commit dfdbedf into main Apr 17, 2026
4 checks passed
@atlas-apex atlas-apex deleted the feature/#59-migration-gate branch April 17, 2026 12:30
osama-abu-baker pushed a commit to osama-abu-baker/apexyard that referenced this pull request Jun 3, 2026
…ion skill + migration AgDR template (me2resh#72)

* feat(me2resh#59): ticket-first gate for database migrations + /migration skill + migration AgDR template

Ships Phase 1 of the migration-gate feature per the issue's own
rollout plan. Phase 2 (sam/terraform plan introspection) is deferred —
path-pattern heuristics cover 80% of the real cases and the remaining
20% can be handled by per-project `migration_paths` overrides.

What ships:

1. `.claude/hooks/require-migration-ticket.sh` — new PreToolUse hook on
   Edit/Write/MultiEdit. If the target path matches a migration
   pattern, runs three gates:
     G1: active ticket marker exists (same me2resh#41 two-tier resolution —
         per-project preferred, ops-level fallback)
     G2: the referenced GitHub issue is OPEN and carries the
         `migration` label (default, overridable via
         `.claude/project-config.json` → `migration_label`)
     G3: the issue body contains a reference to a migration AgDR
         matching `docs/agdr/AgDR-\d+-.*migration.*\.md`
   Non-migration paths exit silently; the normal require-active-ticket
   hook then applies. Exempts .claude/, docs/, projects/*/docs/,
   *.md, and *.example up front.

2. `.claude/skills/migration/SKILL.md` — guided flow that produces
   both artefacts in one pass. Asks for migration type, affected
   tables, rollback plan (required non-empty), downtime estimate,
   cross-service consumers, data volume, testing plan, and
   observability. Writes the AgDR first (reversible local write),
   then creates the labelled GitHub issue, then back-fills the AgDR
   with the issue reference. Tells the user to run /start-ticket as
   the next step — doesn't do it automatically so the handoff stays
   explicit.

3. `templates/agdr-migration.md` — AgDR template with migration-
   specific sections: Rollback Plan (explicit, not buried under
   Consequences), Cross-Service Consumers, Testing Plan, and
   Observability. Built on the same shape as the base AgDR template
   (Context / Options Considered / Decision) so existing AgDR
   discipline carries over.

Wiring:

- `.claude/settings.json`: require-migration-ticket.sh registered
  BEFORE require-active-ticket.sh in the PreToolUse chain so
  migration-specific blocking messages surface first.
- `.claude/rules/workflow-gates.md`: new gate 3a row + dedicated
  "Migration Gate" section with default path list and enforcement
  note.
- `.claude/hooks/README.md`: new section 1a documenting the hook's
  three gates. Hook #1 (require-active-ticket) description updated
  to include the me2resh#41 two-tier resolution.
- `CLAUDE.md`: hook count 16 → 17, skill count 31 → 32, templates
  table gains Migration AgDR row, skills table gains /migration row.
- `workflows/sdlc.md`: new "Sub-Workflow: Database Migrations"
  section after Phase 3 Exit Criteria, showing the flow from
  /migration → /start-ticket → edit → dev smoke → staging →
  prod → monitor.

Smoke-tested the hook with 11 path-matching cases in an isolated
/tmp mock ops root (/tmp/smoke-59.sh). All pass — non-migration
paths pass through, every migration-pattern path blocks when no
marker exists. Gates 2 and 3 require live gh calls and are covered
by real-usage flow (documented in the test output).

Closes me2resh#59

* fix: shellcheck SC2221/SC2222/SC2254 + markdownlint MD032 on me2resh#59

- require-migration-ticket.sh: remove subsumed case patterns (*/projects/*/docs/* under */docs/*, */migrations/*/*.sql under */migrations/*.sql) and disable SC2254 on the intentional glob-expansion in the custom-paths case.
- agdr-migration.md: add blank line before a bulleted list under 'Deploy-order constraint'.

All 11 smoke cases still pass.

---------

Co-authored-by: me2resh <ahmed.abdelaliem@gmail.com>
mosta7il pushed a commit to mosta7il/apexyard that referenced this pull request Jun 8, 2026
…ion skill + migration AgDR template (me2resh#72)

* feat(me2resh#59): ticket-first gate for database migrations + /migration skill + migration AgDR template

Ships Phase 1 of the migration-gate feature per the issue's own
rollout plan. Phase 2 (sam/terraform plan introspection) is deferred —
path-pattern heuristics cover 80% of the real cases and the remaining
20% can be handled by per-project `migration_paths` overrides.

What ships:

1. `.claude/hooks/require-migration-ticket.sh` — new PreToolUse hook on
   Edit/Write/MultiEdit. If the target path matches a migration
   pattern, runs three gates:
     G1: active ticket marker exists (same me2resh#41 two-tier resolution —
         per-project preferred, ops-level fallback)
     G2: the referenced GitHub issue is OPEN and carries the
         `migration` label (default, overridable via
         `.claude/project-config.json` → `migration_label`)
     G3: the issue body contains a reference to a migration AgDR
         matching `docs/agdr/AgDR-\d+-.*migration.*\.md`
   Non-migration paths exit silently; the normal require-active-ticket
   hook then applies. Exempts .claude/, docs/, projects/*/docs/,
   *.md, and *.example up front.

2. `.claude/skills/migration/SKILL.md` — guided flow that produces
   both artefacts in one pass. Asks for migration type, affected
   tables, rollback plan (required non-empty), downtime estimate,
   cross-service consumers, data volume, testing plan, and
   observability. Writes the AgDR first (reversible local write),
   then creates the labelled GitHub issue, then back-fills the AgDR
   with the issue reference. Tells the user to run /start-ticket as
   the next step — doesn't do it automatically so the handoff stays
   explicit.

3. `templates/agdr-migration.md` — AgDR template with migration-
   specific sections: Rollback Plan (explicit, not buried under
   Consequences), Cross-Service Consumers, Testing Plan, and
   Observability. Built on the same shape as the base AgDR template
   (Context / Options Considered / Decision) so existing AgDR
   discipline carries over.

Wiring:

- `.claude/settings.json`: require-migration-ticket.sh registered
  BEFORE require-active-ticket.sh in the PreToolUse chain so
  migration-specific blocking messages surface first.
- `.claude/rules/workflow-gates.md`: new gate 3a row + dedicated
  "Migration Gate" section with default path list and enforcement
  note.
- `.claude/hooks/README.md`: new section 1a documenting the hook's
  three gates. Hook #1 (require-active-ticket) description updated
  to include the me2resh#41 two-tier resolution.
- `CLAUDE.md`: hook count 16 → 17, skill count 31 → 32, templates
  table gains Migration AgDR row, skills table gains /migration row.
- `workflows/sdlc.md`: new "Sub-Workflow: Database Migrations"
  section after Phase 3 Exit Criteria, showing the flow from
  /migration → /start-ticket → edit → dev smoke → staging →
  prod → monitor.

Smoke-tested the hook with 11 path-matching cases in an isolated
/tmp mock ops root (/tmp/smoke-59.sh). All pass — non-migration
paths pass through, every migration-pattern path blocks when no
marker exists. Gates 2 and 3 require live gh calls and are covered
by real-usage flow (documented in the test output).

Closes me2resh#59

* fix: shellcheck SC2221/SC2222/SC2254 + markdownlint MD032 on me2resh#59

- require-migration-ticket.sh: remove subsumed case patterns (*/projects/*/docs/* under */docs/*, */migrations/*/*.sql under */migrations/*.sql) and disable SC2254 on the intentional glob-expansion in the custom-paths case.
- agdr-migration.md: add blank line before a bulleted list under 'Deploy-order constraint'.

All 11 smoke cases still pass.

---------

Co-authored-by: me2resh <ahmed.abdelaliem@gmail.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

[Feature] Ticket-first gate for database migrations + /migration skill + migration AgDR template

2 participants