📦 deps(thirdparty): update snapshots

This commit is contained in:
ci[bot]
2026-06-27 16:04:15 +00:00
parent 07735e783a
commit 6f601609c1
221 changed files with 9641 additions and 620 deletions
@@ -1,7 +1,7 @@
{
"name": "antigravity-awesome-skills",
"version": "13.3.0",
"description": "Plugin-safe Claude Code distribution of Antigravity Awesome Skills with 1,647 supported skills.",
"version": "13.4.0",
"description": "Plugin-safe Claude Code distribution of Antigravity Awesome Skills with 1,651 supported skills.",
"author": {
"name": "sickn33 and contributors",
"url": "https://github.com/sickn33/antigravity-awesome-skills"
@@ -0,0 +1,136 @@
---
name: ai-loop
description: Runs a bounded spec-build-review development loop with explicit scope, stop conditions, and human approval gates for risky or ambiguous work.
category: workflow
risk: safe
source: community
date_added: "2026-06-27"
tags: [agent-workflow, specification, implementation, review, verification, feedback-loop]
tools: [claude, cursor, codex, gemini]
---
# AI-Loop Skill
## Overview
The `ai-loop` skill structures a bounded development cycle for agentic workflows. By dividing the process into distinct planning (Spec), implementation (Build), and validation (Review) phases, it helps an agent build and correct scoped code changes while keeping requirements, risk gates, and stop conditions explicit.
## When to Use This Skill
- Use when you need a feature built from scratch or heavily modified, and you want the agent to handle the lifecycle (specification, implementation, and verification) inside one clearly bounded workflow.
- Use when working with isolated components, modules, or features that have well-defined scopes and constraints.
- Use when the user asks for a complete development pass but the work still has clear success criteria, a reasonable verification path, and no unresolved safety or product decisions.
## How It Works
This skill executes a controlled development loop composed of three phases: Spec, Build, and Review. When invoked, the agent moves through those phases until the scoped requirements pass verification, a stop condition is reached, or human approval is needed.
Before starting, define:
- The maximum number of build-review iterations.
- The verification commands or manual checks that count as evidence.
- The actions that require explicit approval, such as destructive commands, production changes, external service writes, or broad architectural pivots.
### Phase 1: Spec (Planning)
1. Interview the user about the feature or app they want to build. Ask one focused question at a time until you fully understand the goal, the must-have requirements, the constraints, and what "done" looks like.
2. **Do not start building yet.**
3. When you have enough information, write a clear, detailed specification and save it to `specs/<feature-name>.md`.
4. The spec must include:
- The objective
- The exact requirements
- Edge cases to handle
- A concrete definition of done that someone could check the build against
- The iteration budget, verification commands, and approval gates.
### Phase 2: Build (Implementation)
1. Read the spec you just created in `specs/<feature-name>.md`.
2. Build exactly what it describes.
3. **Do not add features**, do not refactor unrelated code, and do not invent requirements that aren't in the spec.
4. Focus strictly on fulfilling the spec. List which spec requirements you covered so the review step can check them.
### Phase 3: Review (Verification)
1. Compare your implementation against `specs/<feature-name>.md`.
2. Go requirement by requirement and verify if it was met. List every gap, bug, or missing piece, naming the exact spec item each one fails.
3. If anything fails and the iteration budget is not exhausted, write the specific fixes needed and **loop back to Phase 2 (Build)** to address them.
4. Stop and ask for human input when the next fix would change the spec, exceed the iteration budget, require risky operations, or depend on product decisions not captured in the spec.
5. Only pass the build and conclude the skill execution when every requirement in the spec is fully met and the declared verification evidence has passed.
## Examples
### Example 1: Creating a Basic Statistics Helper
**User:**
> I want to add a utility for calculating basic statistics (mean, median, mode) of an array of numbers.
**Agent (Phase 1 - Spec):**
> I can help with that. To get started, what programming language should this statistics calculator be implemented in? Also, how should empty arrays or invalid inputs be handled?
**User:**
> JavaScript. If the array is empty, return null. If there are non-numbers, ignore them.
**Agent (Phase 1 - Spec):**
> Thank you. I have created the specification file at `specs/stats-calculator.md` with the requirements we discussed. Now, I will start implementing it.
**Agent (Phase 2 - Build):**
> *(Creates `stats_calculator.js` and implements `mean`, `median`, and `mode` matching the spec exactly)*
> I have finished implementing the calculator. I will now verify the implementation.
**Agent (Phase 3 - Review):**
> Checking implementation against `specs/stats-calculator.md`:
> - Requirement: mean calculation -> PASS
> - Requirement: median calculation -> PASS
> - Requirement: mode calculation -> PASS
> - Requirement: empty array returns null -> PASS
> - Requirement: non-numbers are ignored -> FAIL (I noticed `mode` doesn't filter out non-numbers).
>
> I will now loop back to Phase 2 to fix the `mode` function.
**Agent (Phase 2 - Build Loop-Back):**
> *(Fixes `mode` to filter out non-numbers)*
> I have updated the implementation. Let's re-verify.
**Agent (Phase 3 - Review):**
> - Requirement: mode filters non-numbers -> PASS
>
> All requirements passed! The statistics calculator is complete and verified.
## Best Practices
- ✅ Do ask clarifying questions one at a time to avoid overwhelming the user during the planning phase.
- ✅ Do document edge cases explicitly in `specs/<feature-name>.md` before writing any code.
- ✅ Do stick strictly to the approved specification during the build phase.
- ✅ Do cap the loop with a small iteration budget and report exactly what remains if the budget is exhausted.
- ✅ Do pause for explicit approval before destructive, production, credentialed, or externally visible actions.
- ❌ Don't implement extra features or perform unrelated refactorings that aren't specified.
- ❌ Don't skip the review phase or pass it without verifying every single requirement.
- ❌ Don't keep retrying the same failing fix without new evidence or a changed approach.
## Limitations
- This skill requires sufficient context about the feature to be provided during the Spec phase.
- It is best suited for isolated features or tasks with clear boundaries, rather than open-ended architectural refactoring.
- The review phase relies on the agent's self-assessment against the generated spec; manual review is still recommended for critical systems.
- It is not a replacement for human approval on security-sensitive, destructive, production, compliance, or externally visible changes.
- It should stop rather than continue if requirements conflict, tests cannot run, or verification depends on unavailable credentials or systems.
## Security & Safety Notes
- Be cautious when running or testing code generated during the Build phase. Always run tests in a safe, sandboxed environment.
- Avoid executing arbitrary shell commands provided directly by the user without validating their safety.
- Make sure no hardcoded secrets, keys, or credentials are added to the code or specifications.
- Treat production deploys, data migrations, payment flows, credential changes, and external write actions as approval-gated work.
## Common Pitfalls
- **Problem:** The agent tries to build a huge system all at once, leading to an overcomplicated spec and incomplete implementation.
**Solution:** Keep the scope of `ai-loop` to small, modular features. Break larger systems into multiple independent loops.
- **Problem:** The spec is vague, causing the build phase to rely on assumptions.
**Solution:** Spend extra time in the planning phase asking targeted questions to pin down requirements.
## Related Skills
- `@plan-writing` - For writing more detailed implementation plans for larger projects.
- `@ask-questions-if-underspecified` - For standard guidelines on interviewing the user.
@@ -0,0 +1,244 @@
---
name: cron-doctor
description: "Diagnose and validate cron expressions before they ship. Catches the five silent death-traps: impossible dates that never fire, OR-semantics that fire too often, midnight spikes, uneven step drift, and leap-year February 29."
category: devops
risk: safe
source: community
source_repo: takeaseatventure/devops-skills
source_type: community
date_added: "2026-06-26"
author: takeaseat
tags: [cron, crontab, scheduling, devops, debugging, kubernetes, validation]
tools: [claude, cursor, codex, gemini, opencode]
license: "MIT"
license_source: "https://github.com/takeaseatventure/devops-skills/blob/main/LICENSE"
---
# cron-doctor
## Overview
Cron is deceptively error-prone. The failure mode is **silent** — a syntactically
valid expression that simply never fires, or fires far more often than intended.
`0 0 30 2 *` parses cleanly and then sits dead forever (February has no 30th).
`0 0 1,15 * 1` looks like "1st and 15th if Monday" but actually means "1st, 15th,
**OR** every Monday" — ~6 fires/month instead of ~2.
This skill teaches an agent to catch those before they reach production. It comes
with a zero-dependency validation engine (`scripts/cron-engine.js`, no install
needed) that parses, describes, deep-validates, and computes next fire times.
## When to Use This Skill
- Use when a user writes, edits, reviews, or deploys a cron expression — in a
crontab, a Kubernetes `CronJob`, a GitHub Actions `schedule`, an Airflow DAG,
a Celery beat schedule, a systemd timer, or any scheduled task.
- Use when debugging a job that "didn't fire" or "fired at the wrong time."
- Use when a user asks "what does this cron expression mean?" or "when will this
run next?" or "how often does this run per year?"
- Use when reviewing a CI/CD pipeline or infrastructure config that contains a
`schedule` field.
- Use when a user pastes a 5-field cron expression and asks for a sanity check.
## How It Works
### Step 1: Parse the expression
Split on whitespace into 5 fields: minute, hour, day-of-month, month, day-of-week.
Confirm valid ranges:
| Field | Position | Range | Notes |
|-------|----------|-------|-------|
| minute | 1 | 059 | |
| hour | 2 | 023 | |
| day-of-month | 3 | 131 | |
| month | 4 | 112 | names (JANDEC) accepted |
| day-of-week | 5 | 07 | 0 and 7 both = Sunday; names (SUNSAT) accepted |
### Step 2: Describe it in plain English
State what the user *thinks* it does vs. what it *actually* does. Be explicit
about OR-vs-AND semantics for day-of-month + day-of-week (see death-trap #2).
### Step 3: Run the trap checklist
Check the five death-traps below and flag any that apply.
### Step 4: Calculate next runs and annual fire count
Compute the next 5 fire times as concrete dates so the user can verify the
schedule behaves as expected. Estimate annual fire count — a schedule that fires
365×/year vs. 12×/year is a ~30× cost and load difference.
## The Five Cron Death-Traps
These are the bugs that pass `crontab -l` validation but break in production.
### 1. Impossible dates — the "never fires" bug
```
0 0 30 2 *
```
**Valid syntax. Never fires.** February has no 30th. This schedule is a dead job
that silently sits forever. The same applies to day 31 in any 30-day month:
`0 0 31 4 *`, `0 0 31 6 *`, `0 0 31 9 *`, `0 0 31 11 *`.
**Fix:** use `0 0 28-31 * *` and check for end-of-month in the script, or use `L`
(last day) syntax if your scheduler supports it.
### 2. OR-semantics — the "fires too often" bug
```
0 0 1,15 * 1
```
**Does NOT mean** "midnight on the 1st and 15th if it's Monday."
**Does mean** "midnight on the 1st, the 15th, **OR** every Monday." That's ~6
fires/month instead of ~2.
This is the single most misunderstood cron rule. When **both** day-of-month AND
day-of-week are restricted (neither is `*`), cron uses OR logic, not AND.
**Fix:** if you need "1st and 15th only if Monday," run daily and check in the
script:
```bash
0 0 * * 1 [ "$(date +%d)" = "01" -o "$(date +%d)" = "15" ] && your-command
```
### 3. Midnight spike — the "everything at once" bug
```
0 0 * * *
```
Every job scheduled at `0 0` competes for resources at exactly 00:00. Database
backups, log rotations, cert renewals, report generation — all fire simultaneously.
This causes load spikes, connection-pool exhaustion, and cascading timeouts.
**Fix:** stagger jobs across the hour. Use `17 2 * * *` or `43 3 * * *` instead of
`0 0`. Jitter is your friend.
### 4. Uneven steps — the "drift" bug
```
*/7 * * * *
```
**Does NOT mean** "every 7 minutes evenly." It means "every 7 minutes starting at
0, then resets at 60." So: 0, 7, 14, 21, 28, 35, 42, 49, 56 — then 0 again
(a 4-minute gap). The intervals drift: 7,7,7,7,7,7,7,7,**4**.
**Fix:** 60 is not divisible by 7. Use step values that divide 60 evenly: `*/5`,
`*/10`, `*/15`, `*/20`, `*/30`. If you truly need every-7-minutes, use a loop with
`sleep 420`.
### 5. Leap-year February 29 — the "annual surprise"
```
0 0 29 2 *
```
Fires only on leap years — February 29, 2024 / 2028 / 2032… If someone writes this
expecting "end of February," they'll be confused for 3 out of every 4 years.
**Fix:** use `0 0 28 2 *` and handle the 29th case in the script if needed.
## Using the validation script
This skill ships a zero-dependency engine at `scripts/cron-engine.js` (Node.js, no
`npm install` needed). You can use it programmatically or from the CLI:
```javascript
// Programmatic — Node.js, zero dependencies
const { describe, validate, nextRuns, formatNextRuns } = require('./scripts/cron-engine.js');
// Parse + describe -> returns { text, error, parsed }
const d = describe('0 0 30 2 *');
console.log(d.text); // "At 00:00, on day-of-month 30 in in FEB"
// Deep validation -> catches the traps
const result = validate('0 0 30 2 *');
console.log(result.valid); // true (syntax is valid)
console.log(result.observations); // includes the "never fires" insight
console.log(result.suggestions); // e.g. "Midnight is a common spike..."
// Next 5 fire times -> returns Date[]
const runs = nextRuns('0 9 * * 1-5', new Date(), 5);
console.log(formatNextRuns(runs, new Date())); // [{ date, relative, formatted }, ...]
```
```bash
# CLI (via the bundled wrapper)
node scripts/cli.js describe "*/5 * * * *"
node scripts/cli.js validate "0 0 30 2 *"
node scripts/cli.js next "0 9 * * 1-5" 5
```
## Common cron presets
| Expression | Description | Use case |
|-----------|-------------|----------|
| `*/5 * * * *` | Every 5 minutes | Health checks, polling |
| `0 * * * *` | Every hour | Hourly aggregation |
| `0 */2 * * *` | Every 2 hours | Semi-frequent sync |
| `0 9 * * 1-5` | 9am MonFri | Business-hours task |
| `0 2 * * *` | 2am daily | Off-peak batch (avoid midnight) |
| `0 0 * * 0` | Midnight Sunday | Weekly maintenance |
| `0 0 1 * *` | Midnight 1st of month | Monthly report |
| `0 0 1 1 *` | Midnight Jan 1st | Annual task |
## Best Practices
- ✅ Always provide the plain-English description AND run the trap checklist.
- ✅ Stagger midnight jobs to avoid the spike.
- ✅ Prefer step values that divide 60 evenly (`*/5`, `*/15`, `*/30`).
- ✅ Add a comment above every crontab line explaining intent.
- ✅ Set an explicit timezone (`CRON_TZ`) on schedulers that support it.
- ❌ Don't trust `crontab -l` validation — it only checks syntax, not semantics.
- ❌ Don't restrict both day-of-month and day-of-week without confirming OR-logic.
- ❌ Don't schedule everything at `0 0`.
## Common Pitfalls
- **Problem:** "My cron job isn't running."
**Solution:** Check for an impossible date (trap #1) and confirm the daemon is
running (`service cron status` / `systemctl status crond`). Verify the file
ends with a newline and has correct ownership.
- **Problem:** "My job runs far more often than expected."
**Solution:** You hit OR-semantics (trap #2). If both day-of-month and
day-of-week are set, cron ORs them. Move one to `*` or guard in-script.
- **Problem:** "Intervals are uneven — sometimes 7 min, sometimes 4."
**Solution:** Step value doesn't divide 60 evenly (trap #4). Use a divisor of 60.
- **Problem:** "My job works locally but not in the cluster."
**Solution:** Timezone mismatch. Kubernetes `CronJob` and GitHub Actions default
to UTC. Confirm `timeZone` / `TZ` is set as intended.
## Limitations
- This skill targets standard 5-field cron as implemented by Vixie cron, systemd
timers, Kubernetes `CronJob`, GitHub Actions `schedule`, and most libraries. It
does **not** validate Quartz 6/7-field expressions with seconds/years, nor
non-standard `@reboot` / `L` / `#` extensions without a note.
- Estimated annual fire counts assume a non-leap reference year; February 29
schedules (trap #5) are flagged explicitly.
- This skill does not replace environment-specific validation, testing, or expert
review. Stop and ask for clarification if required inputs, permissions, or
safety boundaries are missing.
## Related Skills
- `docker-expert` — when the cron job runs inside a container and the issue is the
container/entrypoint rather than the schedule.
- `kubernetes-deployment` — when validating a `CronJob` manifest's `spec.schedule`
field alongside the broader resource config.
## Security & Safety Notes
This skill is read-only and `risk: safe`. The validation script performs no file
writes, network calls, or mutations — it only parses and computes. It is safe to
run against any cron expression without preconditions.
@@ -0,0 +1,75 @@
#!/usr/bin/env node
'use strict';
// Minimal CLI wrapper for cron-engine.js. Zero dependencies.
// Usage:
// node cli.js describe "<cron>"
// node cli.js validate "<cron>"
// node cli.js next "<cron>" [count]
const cron = require('./cron-engine.js');
const expr = process.argv[3];
const cmd = process.argv[2];
if (!cmd || !expr) {
console.error('Usage: node cli.js <describe|validate|next> "<cron-expr>" [count]');
console.error('Examples:');
console.error(' node cli.js describe "*/5 * * * *"');
console.error(' node cli.js validate "0 0 30 2 *"');
console.error(' node cli.js next "0 9 * * 1-5" 5');
process.exit(2);
}
function safe(fn) {
try {
fn();
} catch (e) {
console.error('Error: ' + (e.message || e));
process.exit(1);
}
}
switch (cmd) {
case 'describe':
safe(() => {
const d = cron.describe(expr);
console.log(d.text || d.description || JSON.stringify(d));
});
break;
case 'validate':
safe(() => {
const r = cron.validate(expr);
console.log('valid: ' + r.valid);
if (r.description) console.log('description: ' + r.description);
if (r.warnings && r.warnings.length) {
console.log('warnings:');
r.warnings.forEach((w) => console.log(' - ' + w));
}
if (r.observations && r.observations.length) {
console.log('observations:');
r.observations.forEach((o) => console.log(' [' + (o.level || 'info') + '] ' + o.message));
}
if (r.suggestions && r.suggestions.length) {
console.log('suggestions:');
r.suggestions.forEach((s) => console.log(' [' + (s.level || 'info') + '] ' + s.message));
}
});
break;
case 'next':
safe(() => {
const count = parseInt(process.argv[4] || '5', 10);
const runs = cron.nextRuns(expr, new Date(), count);
const formatted = cron.formatNextRuns(runs, new Date());
formatted.forEach((f) =>
console.log(f.relative + '\t' + f.formatted + '\t' + f.date.toString())
);
});
break;
default:
console.error('Unknown command: ' + cmd);
console.error('Commands: describe, validate, next');
process.exit(2);
}
@@ -0,0 +1,638 @@
'use strict';
// ============================================================================
// cron.js — Cron expression parser, describer, validator, and next-run engine.
// Zero dependencies. Extracted from the DevRef Cron Expression Generator
// (battle-tested in browser) and extended with validate() for Pro insights.
// ============================================================================
const MONTH_NAMES = ['JAN','FEB','MAR','APR','MAY','JUN','JUL','AUG','SEP','OCT','NOV','DEC'];
const DAY_NAMES = ['SUN','MON','TUE','WED','THU','FRI','SAT'];
const FIELDS = [
{ name: 'minute', min: 0, max: 59, key: 'minute' },
{ name: 'hour', min: 0, max: 23, key: 'hour' },
{ name: 'dom', min: 1, max: 31, key: 'dom' },
{ name: 'month', min: 1, max: 12, key: 'month', named: MONTH_NAMES },
{ name: 'dow', min: 0, max: 7, key: 'dow', named: DAY_NAMES },
];
class CronError extends Error {
constructor(message, fieldIndex) {
super(message);
this.name = 'CronError';
this.fieldIndex = fieldIndex;
}
}
// ---- Name resolution ----
function resolveName(token, names) {
if (!names) return null;
const up = token.toUpperCase();
const idx = names.indexOf(up);
return idx === -1 ? null : idx;
}
// ---- Field parsing ----
function parseField(raw, fieldDef, fieldIndex) {
const trimmed = String(raw).trim();
if (trimmed === '') throw new CronError(`Field ${fieldIndex + 1} (${fieldDef.name}) is empty`, fieldIndex);
const out = { raw: trimmed, values: null, special: null };
// Special: day-of-week "#" (nth weekday)
if (fieldDef.key === 'dow' && trimmed.includes('#')) {
const m = trimmed.match(/^([0-7A-Za-z]+)#([1-5])$/);
if (!m) throw new CronError(`Invalid "#" syntax in day-of-week: "${trimmed}"`, fieldIndex);
let dowNum = parseSingleNum(m[1], fieldDef, fieldIndex);
if (dowNum === 7) dowNum = 0;
out.special = { kind: 'hash', dow: dowNum, nth: parseInt(m[2], 10) };
return out;
}
// Special: day-of-week "L" (last weekday)
if (fieldDef.key === 'dow' && /L$/i.test(trimmed)) {
const m = trimmed.match(/^([0-7A-Za-z]+)L$/i);
if (!m) throw new CronError(`Invalid "L" syntax in day-of-week: "${trimmed}"`, fieldIndex);
let dowNum = parseSingleNum(m[1], fieldDef, fieldIndex);
if (dowNum === 7) dowNum = 0;
out.special = { kind: 'dowLast', dow: dowNum };
return out;
}
// Special: day-of-month "L" (last day)
if (fieldDef.key === 'dom' && /^L/i.test(trimmed)) {
const m = trimmed.match(/^L(?:-(\d+))?$/i);
if (!m) throw new CronError(`Invalid "L" syntax in day-of-month: "${trimmed}"`, fieldIndex);
out.special = { kind: 'domLast', offset: m[1] ? parseInt(m[1], 10) : 0 };
return out;
}
// Special: day-of-month "W" (nearest weekday)
if (fieldDef.key === 'dom' && /W$/i.test(trimmed)) {
const m = trimmed.match(/^(\d+)W$/i);
if (!m) throw new CronError(`Invalid "W" syntax in day-of-month: "${trimmed}"`, fieldIndex);
const day = parseInt(m[1], 10);
if (day < fieldDef.min || day > fieldDef.max) {
throw new CronError(`Day-of-month "${day}W" out of range (${fieldDef.min}-${fieldDef.max})`, fieldIndex);
}
out.special = { kind: 'weekday', day: day };
return out;
}
// Standard parsing
const values = new Set();
const items = trimmed.split(',');
for (const item of items) {
parseItem(item, fieldDef, fieldIndex, values);
}
out.values = values;
return out;
}
function parseSingleNum(token, fieldDef, fieldIndex) {
const n = parseInt(token, 10);
if (!isNaN(n)) return n;
const named = resolveName(token, fieldDef.named);
if (named !== null) {
return fieldDef.key === 'month' ? named + 1 : named;
}
throw new CronError(`Invalid value "${token}" in ${fieldDef.name}`, fieldIndex);
}
function parseItem(item, fieldDef, fieldIndex, values) {
const t = item.trim();
if (t === '') throw new CronError(`Empty item in ${fieldDef.name}`, fieldIndex);
if (t === '*') {
addRange(values, fieldDef.min, fieldDef.max, fieldDef);
return;
}
if (t.includes('/')) {
const [base, stepStr] = t.split('/');
const step = parseInt(stepStr, 10);
if (isNaN(step) || step < 1) throw new CronError(`Invalid step "${stepStr}" in ${fieldDef.name}`, fieldIndex);
let lo, hi;
if (base === '*' || base === '') {
lo = fieldDef.min; hi = fieldDef.max;
} else if (base.includes('-')) {
const [a, b] = base.split('-');
lo = parseSingleNum(a.trim(), fieldDef, fieldIndex);
hi = parseSingleNum(b.trim(), fieldDef, fieldIndex);
} else {
lo = parseSingleNum(base.trim(), fieldDef, fieldIndex);
hi = fieldDef.max;
}
if (lo > hi) [lo, hi] = [hi, lo];
for (let v = lo; v <= hi; v += step) addOne(values, v, fieldDef, fieldIndex);
return;
}
if (t.includes('-')) {
const parts = t.split('-');
if (parts.length !== 2) throw new CronError(`Invalid range "${t}" in ${fieldDef.name}`, fieldIndex);
const a = parseSingleNum(parts[0].trim(), fieldDef, fieldIndex);
const b = parseSingleNum(parts[1].trim(), fieldDef, fieldIndex);
addRange(values, a, b, fieldDef);
return;
}
const v = parseSingleNum(t, fieldDef, fieldIndex);
addOne(values, v, fieldDef, fieldIndex);
}
function addOne(values, v, fieldDef, fieldIndex) {
if (fieldDef.key === 'dow' && v === 7) { values.add(0); return; }
if (v < fieldDef.min || v > fieldDef.max) {
throw new CronError(`Value ${v} out of range for ${fieldDef.name} (${fieldDef.min}-${fieldDef.max})`, fieldIndex);
}
values.add(v);
}
function addRange(values, lo, hi, fieldDef) {
if (lo > hi) [lo, hi] = [hi, lo];
if (lo < fieldDef.min || hi > fieldDef.max) {
throw new CronError(`Range ${lo}-${hi} out of bounds for ${fieldDef.name} (${fieldDef.min}-${fieldDef.max})`, -1);
}
for (let v = lo; v <= hi; v++) {
if (fieldDef.key === 'dow' && v === 7) { values.add(0); continue; }
values.add(v);
}
}
// ---- Full expression parser ----
function parseCron(expr) {
const parts = String(expr).trim().split(/\s+/);
if (parts.length !== 5) {
throw new CronError(`Expected 5 fields (got ${parts.length}). Format: minute hour day-of-month month day-of-week`, -1);
}
const parsed = {};
for (let i = 0; i < 5; i++) {
parsed[FIELDS[i].key] = parseField(parts[i], FIELDS[i], i);
}
parsed.domRestricted = !/^\s*\*\s*$/.test(parts[2]);
parsed.dowRestricted = !/^\s*\*\s*$/.test(parts[4]);
parsed.parts = parts;
return parsed;
}
// ---- Human-readable description ----
function describe(expr) {
let parsed;
try { parsed = parseCron(expr); } catch (e) { return { text: e.message, error: true }; }
return { text: describeParsed(parsed), error: false, parsed };
}
function describeParsed(p) {
const monthDesc = describeFieldMonth(p.month);
const domDesc = describeFieldDom(p.dom);
const dowDesc = describeFieldDow(p.dow);
const isEveryMin = p.parts[0] === '*';
const isEveryHour = p.parts[1] === '*';
let timePart = '';
if (isEveryMin && isEveryHour) {
timePart = 'At every minute';
} else if (isEveryMin && !isEveryHour) {
const hours = [...(p.hour.values || [])].sort((a, b) => a - b);
if (hours.length > 0) {
timePart = 'Every minute during the ' + hours.map(h => pad2(h)).join(', ') + ' hour' + (hours.length > 1 ? 's' : '');
} else {
timePart = 'Every minute';
}
} else {
timePart = 'At ' + describeTimes(p.minute, p.hour);
}
let dayPart = '';
const domAny = !p.domRestricted;
const dowAny = !p.dowRestricted;
if (domAny && dowAny) {
if (monthDesc.restricted) {
dayPart = ', ' + monthDesc.text + ' of every year';
} else {
dayPart = ', every day';
}
} else if (!domAny && dowAny) {
dayPart = ', on ' + domDesc.text;
if (monthDesc.restricted) dayPart += ' in ' + monthDesc.text;
} else if (domAny && !dowAny) {
dayPart = ', on ' + dowDesc.text;
if (monthDesc.restricted) dayPart += ' in ' + monthDesc.text;
} else {
dayPart = ', on ' + domDesc.text + ' and on ' + dowDesc.text;
if (monthDesc.restricted) dayPart += ' in ' + monthDesc.text;
}
return capitalize(timePart + dayPart);
}
function describeTimes(minuteField, hourField) {
const mins = [...(minuteField.values || [])].sort((a, b) => a - b);
const hours = [...(hourField.values || [])].sort((a, b) => a - b);
if (pIsWildcard(hourField) && !pIsWildcard(minuteField)) {
if (mins.length === 1) return `minute ${mins[0]} of every hour`;
return `minutes ${listJoin(mins)} of every hour`;
}
if (pIsWildcard(minuteField) && pIsWildcard(hourField)) return 'every minute of every hour';
if (pIsWildcard(minuteField)) {
return `every minute during the ${hours.map(h => pad2(h)).join(', ')} hour${hours.length > 1 ? 's' : ''}`;
}
const combos = [];
for (const h of hours) {
for (const m of mins) {
combos.push(formatHM(h, m));
}
}
return listJoin(combos);
}
function describeFieldMonth(field) {
if (pIsWildcard(field)) return { restricted: false, text: 'every month' };
const vals = [...(field.values || [])].sort((a, b) => a - b);
return { restricted: true, text: 'in ' + listJoin(vals.map(v => capitalize(MONTH_NAMES[v - 1]))) };
}
function describeFieldDom(field) {
if (pIsWildcard(field)) return { text: 'every day-of-month' };
if (field.special) {
if (field.special.kind === 'domLast') {
return { text: field.special.offset === 0 ? 'the last day of the month' : `the last day of the month minus ${field.special.offset} days` };
}
if (field.special.kind === 'weekday') {
return { text: `the nearest weekday to day ${field.special.day}` };
}
}
const vals = [...(field.values || [])].sort((a, b) => a - b);
return { text: `day-of-month ${listJoin(vals)}` };
}
function describeFieldDow(field) {
if (pIsWildcard(field)) return { text: 'every day-of-week' };
if (field.special) {
if (field.special.kind === 'hash') {
return { text: `the ${ordinal(field.special.nth)} ${capitalize(DAY_NAMES[field.special.dow])} of the month` };
}
if (field.special.kind === 'dowLast') {
return { text: `the last ${capitalize(DAY_NAMES[field.special.dow])} of the month` };
}
}
const vals = [...(field.values || [])].sort((a, b) => a - b);
return { text: listJoin(vals.map(v => capitalize(DAY_NAMES[v]))) };
}
function pIsWildcard(field) { return field.raw === '*'; }
// ---- Next run calculator ----
function nextRuns(expr, fromDate, count) {
count = count || 10;
const p = parseCron(expr);
const runs = [];
let d = new Date(fromDate.getTime());
d.setSeconds(0, 0);
d = new Date(d.getTime() + 60000);
let maxScan = 600000; // ~416 days ceiling
while (runs.length < count && maxScan-- > 0) {
if (matches(d, p)) {
runs.push(new Date(d.getTime()));
}
d = new Date(d.getTime() + 60000);
}
return runs;
}
function matches(d, p) {
if (!p.minute.values || !p.minute.values.has(d.getMinutes())) return false;
if (!p.hour.values || !p.hour.values.has(d.getHours())) return false;
if (!p.month.values || !p.month.values.has(d.getMonth() + 1)) return false;
const domAny = !p.domRestricted;
const dowAny = !p.dowRestricted;
let domMatch = false, dowMatch = false;
if (domAny) {
domMatch = true;
} else if (p.dom.special) {
domMatch = matchDomSpecial(d, p.dom.special);
} else if (p.dom.values && p.dom.values.has(d.getDate())) {
domMatch = true;
}
if (dowAny) {
dowMatch = true;
} else if (p.dow.special) {
dowMatch = matchDowSpecial(d, p.dow.special);
} else if (p.dow.values) {
dowMatch = p.dow.values.has(d.getDay());
}
if (domAny && dowAny) return true;
if (!domAny && !dowAny) return domMatch || dowMatch; // OR semantics
return domMatch && dowMatch;
}
function matchDomSpecial(d, special) {
if (special.kind === 'domLast') {
const lastDay = lastDayOfMonth(d.getFullYear(), d.getMonth());
const target = special.offset === 0 ? lastDay : lastDay - special.offset;
return d.getDate() === target;
}
if (special.kind === 'weekday') {
return d.getDate() === nearestWeekday(d.getFullYear(), d.getMonth(), special.day);
}
return false;
}
function matchDowSpecial(d, special) {
if (special.kind === 'hash') {
return nthWeekdayMatches(d, special.dow, special.nth);
}
if (special.kind === 'dowLast') {
return lastWeekdayMatches(d, special.dow);
}
return false;
}
function nthWeekdayMatches(d, dow, nth) {
if (d.getDay() !== dow) return false;
const dayOfMonth = d.getDate();
const occurrence = Math.ceil(dayOfMonth / 7);
return occurrence === nth;
}
function lastWeekdayMatches(d, dow) {
if (d.getDay() !== dow) return false;
const lastDay = lastDayOfMonth(d.getFullYear(), d.getMonth());
return d.getDate() + 7 > lastDay;
}
function lastDayOfMonth(year, month) {
return new Date(year, month + 1, 0).getDate();
}
function nearestWeekday(year, month, day) {
const lastDay = lastDayOfMonth(year, month);
const target = Math.min(day, lastDay);
const dt = new Date(year, month, target);
const wd = dt.getDay();
let result = target;
if (wd === 0) {
if (target + 1 <= lastDay) result = target + 1;
else result = target - 2;
} else if (wd === 6) {
if (target - 1 >= 1) result = target - 1;
else result = target + 2;
}
return result;
}
// ============================================================================
// validate() — Pro-tier feature: deeper analysis of a cron expression.
// Returns warnings, observations, and optimization suggestions.
// ============================================================================
function validate(expr) {
let parsed;
try {
parsed = parseCron(expr);
} catch (e) {
return {
valid: false,
error: e.message,
fieldIndex: e.fieldIndex,
warnings: [],
observations: [],
suggestions: [],
};
}
const warnings = [];
const observations = [];
const suggestions = [];
const desc = describeParsed(parsed);
// Check: day-of-month and day-of-week both restricted (OR semantics surprise)
if (parsed.domRestricted && parsed.dowRestricted) {
warnings.push({
level: 'high',
message: 'Both day-of-month and day-of-week are restricted. Cron uses OR semantics for these fields — the job will run when EITHER matches, not both. This is a common source of bugs.',
});
}
// Check: impossible day-of-month values (e.g., 31 in Feb)
const domValues = [...(parsed.dom.values || [])];
if (!parsed.domRestricted && parsed.month.values && ![...parsed.month.values].every(m => m === 2)) {
// skip
} else if (parsed.domRestricted && !parsed.dom.special && domValues.includes(31)) {
const monthsWith31 = [1, 3, 5, 7, 8, 10, 12]; // Jan, Mar, May, Jul, Aug, Oct, Dec
const monthValues = parsed.month.values ? [...parsed.month.values] : [];
const restrictedMonths = parsed.parts[3] !== '*';
if (restrictedMonths) {
const problemMonths = monthValues.filter(m => !monthsWith31.includes(m));
if (problemMonths.length > 0) {
warnings.push({
level: 'medium',
message: `Day 31 is specified but months ${problemMonths.map(m => capitalize(MONTH_NAMES[m - 1])).join(', ')} have fewer than 31 days. The job will never run in those months.`,
});
}
} else {
observations.push({
level: 'info',
message: 'Day 31 will only match in months with 31 days (7 of 12 months). The job effectively skips Feb, Apr, Jun, Sep, and Nov.',
});
}
}
// Check: high-frequency schedules
if (parsed.parts[0] === '*' && parsed.parts[1] === '*') {
observations.push({
level: 'info',
message: 'This expression runs every minute. For production jobs, consider if this frequency is intentional.',
});
}
// Check: step values that don't divide evenly
for (let i = 0; i < 2; i++) {
const part = parsed.parts[i];
if (part.startsWith('*/')) {
const step = parseInt(part.slice(2), 10);
const range = i === 0 ? 60 : 24;
if (range % step !== 0) {
observations.push({
level: 'info',
message: `Step value */${step} in ${FIELDS[i].name} doesn't divide evenly into ${range}. The last interval will be shorter than the rest (e.g., */7 in minutes goes 0,7,14,...,56, then 0 again — not 63).`,
});
}
}
}
// Check: February 29th edge case
if (parsed.domRestricted && !parsed.dom.special) {
const domVals = [...(parsed.dom.values || [])];
const monthVals = parsed.month.values ? [...parsed.month.values] : [];
if (domVals.includes(29) && monthVals.length === 1 && monthVals[0] === 2) {
warnings.push({
level: 'medium',
message: 'February 29th only occurs in leap years. This job will not run at all in non-leap years (3 out of every 4 years).',
});
}
}
// Check: midnight rush
if (parsed.parts[0] === '0' && parsed.parts[1] === '0') {
suggestions.push({
level: 'info',
message: 'Midnight (00:00) is a common schedule and many systems have concurrent job spikes at this time. Consider offsetting to a few minutes past midnight (e.g., 02 0 * * *) to avoid resource contention.',
});
}
// Check: weekend vs weekday
if (parsed.parts[4] === '1-5') {
observations.push({
level: 'info',
message: 'Weekdays only (Mon-Fri). This job will not run on weekends.',
});
}
// Compute frequency estimate
const freq = estimateFrequency(parsed);
if (freq) {
observations.push({
level: 'info',
message: `Approximate frequency: ${freq.description} (~${freq.runsPerYear} runs per year).`,
});
}
return {
valid: true,
description: desc,
warnings,
observations,
suggestions,
parsed,
};
}
function estimateFrequency(parsed) {
try {
// Count runs over a sample year
const start = new Date(2025, 0, 1, 0, 0, 0, 0);
const end = new Date(2026, 0, 1, 0, 0, 0, 0);
let count = 0;
let d = new Date(start.getTime());
let maxScan = 540000; // ~375 days
while (d < end && maxScan-- > 0) {
if (matches(d, parsed)) count++;
d = new Date(d.getTime() + 60000);
}
let description = '';
if (count >= 525600) description = 'every minute';
else if (count >= 500000) description = 'multiple times per minute';
else if (count >= 8000) description = 'hourly or more';
else if (count >= 300) description = 'daily or more';
else if (count >= 40) description = 'weekly or more';
else if (count >= 8) description = 'monthly or more';
else if (count >= 1) description = 'yearly or less';
else description = 'never (impossible schedule)';
return { description, runsPerYear: count };
} catch (e) {
return null;
}
}
// ---- Presets ----
const PRESETS = [
{ label: 'Every minute', cron: '* * * * *' },
{ label: 'Every 5 min', cron: '*/5 * * * *' },
{ label: 'Every 10 min', cron: '*/10 * * * *' },
{ label: 'Every 15 min', cron: '*/15 * * * *' },
{ label: 'Every 30 min', cron: '*/30 * * * *' },
{ label: 'Hourly', cron: '0 * * * *' },
{ label: 'Every 2 hours', cron: '0 */2 * * *' },
{ label: 'Every 6 hours', cron: '0 */6 * * *' },
{ label: 'Every 12 hours', cron: '0 */12 * * *' },
{ label: 'Daily at midnight', cron: '0 0 * * *' },
{ label: 'Daily 9am', cron: '0 9 * * *' },
{ label: 'Twice daily', cron: '0 9,21 * * *' },
{ label: 'Weekdays 9am', cron: '0 9 * * 1-5' },
{ label: 'Weekends 10am', cron: '0 10 * * 0,6' },
{ label: 'Every Monday', cron: '0 0 * * 1' },
{ label: 'Every Friday', cron: '0 0 * * 5' },
{ label: 'Monthly 1st', cron: '0 0 1 * *' },
{ label: 'Quarterly', cron: '0 0 1 */3 *' },
{ label: 'Yearly Jan 1', cron: '0 0 1 1 *' },
];
const COMMON = [
{ label: 'At 14:30', cron: '30 14 * * *' },
{ label: '9am weekdays', cron: '0 9 * * 1-5' },
{ label: 'Every Mon 8am', cron: '0 8 * * 1' },
{ label: 'Last day of month', cron: '0 0 L * *' },
{ label: '15th, weekday', cron: '0 0 15W * *' },
{ label: '3rd Thursday', cron: '0 0 * * 4#3' },
{ label: 'Last Friday', cron: '0 0 * * 5L' },
{ label: 'Business hours', cron: '0 9-17 * * 1-5' },
{ label: 'Backup nightly', cron: '0 2 * * *' },
];
// ---- Helpers ----
function pad2(n) { return String(n).padStart(2, '0'); }
function formatHM(h, m) { return `${pad2(h)}:${pad2(m)}`; }
function capitalize(s) { return s.charAt(0).toUpperCase() + s.slice(1); }
function ordinal(n) {
const s = ['th', 'st', 'nd', 'rd'];
const v = n % 100;
return n + (s[(v - 20) % 10] || s[v] || s[0]);
}
function listJoin(arr) {
if (arr.length === 0) return '';
if (arr.length === 1) return String(arr[0]);
if (arr.length === 2) return `${arr[0]} and ${arr[1]}`;
return arr.slice(0, -1).join(', ') + ', and ' + arr[arr.length - 1];
}
function formatNextRuns(runs, fromDate) {
return runs.map(r => {
const diff = r.getTime() - fromDate.getTime();
const mins = Math.round(diff / 60000);
let rel;
if (mins < 60) rel = `+${mins}m`;
else if (mins < 2880) rel = `+${Math.round(mins / 60)}h`;
else rel = `+${Math.round(mins / 1440)}d`;
return { date: r, relative: rel, formatted: r.toISOString() };
});
}
module.exports = {
CronError,
FIELDS,
MONTH_NAMES,
DAY_NAMES,
PRESETS,
COMMON,
parseCron,
describe,
describeParsed,
nextRuns,
matches,
validate,
estimateFrequency,
formatNextRuns,
parseField,
parseItem,
parseSingleNum,
resolveName,
lastDayOfMonth,
nearestWeekday,
nthWeekdayMatches,
lastWeekdayMatches,
};
@@ -0,0 +1,131 @@
---
name: sql-sentinel
description: "Audit SQL for the cost & performance anti-patterns that burn warehouse credits. Scores warehouse health 0-100 and outputs a prioritized cost-reduction plan for BigQuery, Snowflake, Redshift, and Postgres."
category: data
risk: safe
source: community
source_repo: takeaseatventure/sql-sentinel
source_type: community
date_added: "2026-06-26"
author: takeaseat
tags: [sql, bigquery, snowflake, redshift, postgres, data-warehouse, cost-optimization, performance, audit, finops]
tools: [claude, cursor, codex, gemini]
license: "MIT"
license_source: "https://github.com/takeaseatventure/sql-sentinel/blob/main/LICENSE"
---
# sql-sentinel
## Overview
A static-analysis skill that audits SQL for the cost & performance anti-patterns that dominate warehouse bills — `SELECT *`, full-table scans, non-sargable predicates, Cartesian joins, the `NOT IN` NULL trap, and 15 more. It scores warehouse query health 0-100 (A-F) and outputs a prioritized cost-reduction plan, each finding with a `why`, a concrete `fix`, and an estimated savings.
Built for analytics engineers (dbt, Looker), data platform teams running FinOps / "reduce cloud spend" initiatives, and anyone reviewing a SQL pull request before it hits production. Works across BigQuery, Snowflake, Redshift, and Postgres. Zero dependencies, MIT licensed.
The executable engine and full rule set live in the source repository: https://github.com/takeaseatventure/sql-sentinel
## When to Use This Skill
- A user writes or reviews a query for BigQuery, Snowflake, Redshift, Postgres, or Spark SQL.
- A user asks "why is this query so slow?" or "why is my warehouse bill so high?"
- A user is about to promote a dashboard query or dbt model to production.
- A data engineer wants a second pair of eyes before a code review or a cost-optimization sweep.
- A team is running a "reduce cloud spend" or FinOps initiative.
## How It Works
The engine splits a SQL script into statements (honoring quotes and comments), runs 20 rules over each statement, scores health 0-100 weighted by severity (critical 25, high 12, medium 5, low 1), and returns a prioritized cost-reduction plan.
### Step 1: Run the audit
Install or clone the source repository, then run the zero-dependency engine:
```bash
git clone https://github.com/takeaseatventure/sql-sentinel.git
cd sql-sentinel
node scripts/sql-sentinel.js path/to/query.sql
```
Or programmatically:
```javascript
const { auditSql } = require('./scripts/sql-sentinel');
const report = auditSql(yourSqlString, { dialect: 'bigquery' });
console.log(report.healthScore); // 0-100
console.log(report.grade); // 'A' | 'B' | 'C' | 'D' | 'E' | 'F'
console.log(report.prioritizedPlan); // array, worst findings first
```
### Step 2: Read the prioritized plan
The output leads with critical findings (Cartesian joins, mass DELETE) and descends to low-severity style issues. Each finding explains *why* it costs money and *how* to fix it.
## Examples
### Example 1: A messy dashboard query
```sql
SELECT DISTINCT *
FROM user_events, raw_logs
WHERE LOWER(event_name) LIKE '%signup%'
AND user_id NOT IN (SELECT id FROM deleted_users)
ORDER BY created_at;
```
The audit scores this 17/100 (grade F) and flags 7 findings:
- CRITICAL: comma-join produces a Cartesian product (can turn a $0.02 query into a $200 query)
- HIGH: `SELECT *` forces full column scan (30-90% wasted bytes on wide tables)
- HIGH: leading-wildcard `LIKE '%signup%'` defeats indexes
- HIGH: `LOWER(event_name)` defeats indexes (non-sargable)
- HIGH: `NOT IN (SELECT ...)` — NULL semantics hazard
- MEDIUM: `SELECT DISTINCT` dedup cost
- MEDIUM: `ORDER BY` without `LIMIT` sorts the full result
### Example 2: A clean, sargable query
```sql
-- This scores 90+/100 (grade A) — no findings
SELECT id, email, created_at
FROM users
WHERE created_at >= TIMESTAMP '2026-01-01'
AND created_at < TIMESTAMP '2026-02-01'
ORDER BY id
LIMIT 100;
```
## The 20 rules (ruleset v1.0.0)
| Rule | Severity | Catches |
|---|---|---|
| SQL001 | high | `SELECT *` full column scan |
| SQL002 | critical | No `WHERE` → full table scan |
| SQL003 | high | `LIKE '%term'` non-sargable |
| SQL004 | high | Function on column kills index |
| SQL005 | critical | `CROSS JOIN` / comma-join |
| SQL006 | medium | `SELECT DISTINCT` dedup cost |
| SQL007 | medium | `ORDER BY` without `LIMIT` |
| SQL008 | high | `NOT IN (SELECT ...)` NULL trap |
| SQL009 | medium | Implicit type cast |
| SQL010 | low | Many `OR`s (use `IN`/`UNION`) |
| SQL011 | medium | `COUNT(DISTINCT)` at scale (use HLL) |
| SQL012 | low | `LIMIT` without `ORDER BY` |
| SQL013 | medium | Scalar subquery in `SELECT` |
| SQL014 | medium | 5+ JOINs broadcast/spill risk |
| SQL015 | high | Fact table, no partition filter |
| SQL017 | low | String concat in `SELECT` |
| SQL018 | medium | Window `OVER ()` no `PARTITION` |
| SQL020 | critical | `DELETE`/`UPDATE` without `WHERE` |
| SQL021 | low | `SELECT *` in `EXISTS`/`IN` |
| SQL022 | medium | `UNION` vs `UNION ALL` |
Run the test suite to verify each rule fires on real SQL:
```bash
cd scripts && node test.js # 26 tests, zero dependencies
```
## Limitations
- This is a **static** analyzer. It finds anti-patterns in the *text* of SQL; it does not read query plans, row counts, or billing. A flagged query on a 100-row table is cheap; the same query on a billion-row table is the problem the rule exists to prevent.
- The fact-table heuristic (SQL015) keys off table *names* (`*_events`, `*_log`) and is advisory, not definitive.
- It does not execute SQL — safe to run on any `.sql` file.
@@ -0,0 +1,149 @@
---
name: web-project-brainstorming
description: Masterclass framework for brainstorming web development projects and page designs. Outlines structural phases for concept, UX flow, styling aesthetics, technical architecture, and SEO.
category: consulting
risk: safe
source: self
source_type: self
date_added: "2026-06-26"
author: Rsmiyani
tags: [brainstorming, project-planning, web-development, product-scoping, design-system, architecture]
tools: [claude, cursor, gemini]
---
# Web Project Brainstorming
## Overview
This skill provides a structured, masterclass-level framework for brainstorming web projects, web applications, or individual page designs at their inception. It guides developers and designers through scoping the core product concept, mapping user flows, defining visual styling aesthetics, selecting the technical stack, and planning for search engine optimization (SEO) and performance.
## When to Use This Skill
- Use at the start of any new web development project or page redesign.
- Use when scoping feature sets, user roles, and interaction patterns for web applications.
- Use when establishing design systems, color tokens, and layout guidelines.
- Use when evaluating tech stacks (e.g., Next.js vs. Vanilla JS, CSS Grid vs. Tailwind).
## How It Works
Execute web project brainstorming sequentially across six structured phases. Ask the user questions one phase at a time to maintain focus and ensure thorough alignment.
### Phase 1: Core Concept & Scoping
Define the product's primary value proposition and scope:
- **Target Audience**: Who is using the website or application?
- **Core Value**: What problem does it solve for users?
- **Key Features**: What are the top 35 mandatory features?
### Phase 2: User Experience (UX) & Information Architecture
Map how users navigate and interact:
- **Page Hierarchy**: What is the sitemap and page structure?
- **User Journeys**: What step-by-step flows do users take to complete key goals?
- **Responsive Layout**: Is the interface mobile-first, desktop-first, or balanced?
### Phase 3: Visual Styling & Design System
Establish the visual guidelines and aesthetic parameters:
- **Design Aesthetic**: Modern, minimalist, brutalist, glassmorphism, or luxury?
- **Color Palette**: What are the primary, secondary, and accent colors? (Prefer tailorable HSL/RGB models over static color keywords).
- **Typography**: Which Google Fonts or system fonts fit the theme? (e.g., Inter, Outfit, Syne).
- **Interactive States**: How do hovers, clicks, transitions, and loading states behave?
### Phase 4: Technical Stack & Architecture
Select the technologies and integration systems:
- **Frontend Framework**: React, Next.js, Vite, Astro, Svelte, or Vanilla HTML/JS?
- **Styling Method**: Vanilla CSS, Tailwind CSS, or CSS Modules?
- **Data & Backend**: REST API, GraphQL, tRPC, Firebase, Supabase, or SQLite?
- **State Management**: Zustand, Context API, Redux, or local React state?
### Phase 5: SEO, Accessibility (A11y), and Performance
Plan for discoverability and fast loading times:
- **SEO Elements**: Title tag structure, meta descriptions, and semantic HTML tag hierarchy.
- **Accessibility**: ARIA labels, semantic tags, keyboard navigation, and color contrast.
- **Performance**: Preloading assets, lazy loading images, server-side rendering (SSR), and CDN delivery.
### Phase 6: MVP Scope & Project Phases
Break the work down into manageable increments:
- **Phase 1 (MVP)**: The absolute minimum viable product needed to deploy.
- **Phase 2 (Enhancements)**: Nice-to-have features, micro-animations, and advanced integrations.
## Examples
### Interactive Questionnaire Prompt Template
Use this prompt layout when initiating a brainstorming session with a client or team member:
```markdown
👋 Let's brainstorm your new web project! We will walk through 6 quick phases.
---
### Phase 1: Core Concept & Scoping
1. What is the main title or working name of this project?
2. Who are the primary target users (e.g., tech-savvy professionals, shoppers, children)?
3. What are the 3 core tasks a user must be able to perform?
---
```
### Brainstorming Output Document Template
Once all phases are complete, generate a markdown blueprint for the project using this template:
```markdown
# Project Blueprint: [Project Name]
## 1. Product Concept
- **Value Proposition**: [Summary]
- **Key Features**:
1. [Feature 1]
2. [Feature 2]
## 2. Information Architecture & UX
- **Pages**: `/index.html`, `/dashboard.html`
- **Primary User Flow**: User signs up -> completes onboarding -> views dashboard.
## 3. Styling & Aesthetics
- **Aesthetic**: Sleek Glassmorphism Dark Mode
- **Color Tokens**:
- Background: `hsl(222, 47%, 11%)`
- Accent/Primary: `hsl(217, 91%, 60%)`
- **Typography**: Inter (Body), Outfit (Headings)
## 4. Technical Architecture
- **Framework**: Next.js (App Router)
- **Styling**: Tailwind CSS
- **Database**: PostgreSQL with Prisma ORM
## 5. SEO & Performance
- **Primary Title**: "[Brand] | [Tagline]"
- **Performance Strategy**: Dynamic image optimization, caching pages via Cloudflare.
## 6. MVP vs Phase 2 Roadmap
- **MVP**: Authentication + core dashboard view.
- **Phase 2**: Real-time notifications and PDF reporting.
```
## Best Practices
- ✅ Ask questions incrementally—never dump all six phases in a single response to avoid cognitive overload.
- ✅ Propose logical defaults (e.g., recommending responsive Tailwind/CSS Grid and standard semantic HTML) if the user is unsure.
- ✅ Ensure semantic HTML layout hierarchy (one `<h1>` per page, sequential `<section>`, `<article>`, `<header>`, `<footer>` elements) is planned from the start.
- ✅ Document explicit non-goals to prevent feature creep.
## Limitations
- This skill focuses on conceptual mapping, architecture, and feature planning; it does not replace the writing of implementation code or system configuration.
- Brainstorming outcomes should be treated as flexible blueprints and refined as technical constraints are discovered during development.
## Security & Safety Notes
- During Phase 4 (Architecture), flag any security requirements (e.g., SSL certificates, CORS policies, secure authentication storage, environment variables protection) early.
- Do not store actual API tokens, passwords, or credentials in design or blueprint documents.
## Common Pitfalls
- **Problem**: Scope Creep (the project expands too quickly before building an MVP).
**Solution**: Enforce Phase 6 strictly. Push nice-to-have features into Phase 2.
- **Problem**: Ignoring mobile design until late in development.
**Solution**: Brainstorm responsive patterns in Phase 2 before deciding on layout style in Phase 3.
## Related Skills
- `@writing-plans` - Organizing structural step-by-step engineering plans.
- `@architecture-decision-records` - Documenting architectural decisions.
- `@ux-flow` - Designing deep user experience flows and interaction details.
@@ -1,6 +1,6 @@
{
"name": "antigravity-awesome-skills",
"version": "13.3.0",
"version": "13.4.0",
"description": "Plugin-safe Codex plugin for the Antigravity Awesome Skills library.",
"author": {
"name": "sickn33 and contributors",
@@ -19,7 +19,7 @@
"skills": "./skills/",
"interface": {
"displayName": "Antigravity Awesome Skills",
"shortDescription": "1,629 plugin-safe skills for coding, security, product, and ops workflows.",
"shortDescription": "1,633 plugin-safe skills for coding, security, product, and ops workflows.",
"longDescription": "Install a plugin-safe Codex distribution of Antigravity Awesome Skills. Skills that still need hardening or target-specific setup remain available in the repo but are excluded from this plugin.",
"developerName": "sickn33 and contributors",
"category": "Productivity",
@@ -0,0 +1,136 @@
---
name: ai-loop
description: Runs a bounded spec-build-review development loop with explicit scope, stop conditions, and human approval gates for risky or ambiguous work.
category: workflow
risk: safe
source: community
date_added: "2026-06-27"
tags: [agent-workflow, specification, implementation, review, verification, feedback-loop]
tools: [claude, cursor, codex, gemini]
---
# AI-Loop Skill
## Overview
The `ai-loop` skill structures a bounded development cycle for agentic workflows. By dividing the process into distinct planning (Spec), implementation (Build), and validation (Review) phases, it helps an agent build and correct scoped code changes while keeping requirements, risk gates, and stop conditions explicit.
## When to Use This Skill
- Use when you need a feature built from scratch or heavily modified, and you want the agent to handle the lifecycle (specification, implementation, and verification) inside one clearly bounded workflow.
- Use when working with isolated components, modules, or features that have well-defined scopes and constraints.
- Use when the user asks for a complete development pass but the work still has clear success criteria, a reasonable verification path, and no unresolved safety or product decisions.
## How It Works
This skill executes a controlled development loop composed of three phases: Spec, Build, and Review. When invoked, the agent moves through those phases until the scoped requirements pass verification, a stop condition is reached, or human approval is needed.
Before starting, define:
- The maximum number of build-review iterations.
- The verification commands or manual checks that count as evidence.
- The actions that require explicit approval, such as destructive commands, production changes, external service writes, or broad architectural pivots.
### Phase 1: Spec (Planning)
1. Interview the user about the feature or app they want to build. Ask one focused question at a time until you fully understand the goal, the must-have requirements, the constraints, and what "done" looks like.
2. **Do not start building yet.**
3. When you have enough information, write a clear, detailed specification and save it to `specs/<feature-name>.md`.
4. The spec must include:
- The objective
- The exact requirements
- Edge cases to handle
- A concrete definition of done that someone could check the build against
- The iteration budget, verification commands, and approval gates.
### Phase 2: Build (Implementation)
1. Read the spec you just created in `specs/<feature-name>.md`.
2. Build exactly what it describes.
3. **Do not add features**, do not refactor unrelated code, and do not invent requirements that aren't in the spec.
4. Focus strictly on fulfilling the spec. List which spec requirements you covered so the review step can check them.
### Phase 3: Review (Verification)
1. Compare your implementation against `specs/<feature-name>.md`.
2. Go requirement by requirement and verify if it was met. List every gap, bug, or missing piece, naming the exact spec item each one fails.
3. If anything fails and the iteration budget is not exhausted, write the specific fixes needed and **loop back to Phase 2 (Build)** to address them.
4. Stop and ask for human input when the next fix would change the spec, exceed the iteration budget, require risky operations, or depend on product decisions not captured in the spec.
5. Only pass the build and conclude the skill execution when every requirement in the spec is fully met and the declared verification evidence has passed.
## Examples
### Example 1: Creating a Basic Statistics Helper
**User:**
> I want to add a utility for calculating basic statistics (mean, median, mode) of an array of numbers.
**Agent (Phase 1 - Spec):**
> I can help with that. To get started, what programming language should this statistics calculator be implemented in? Also, how should empty arrays or invalid inputs be handled?
**User:**
> JavaScript. If the array is empty, return null. If there are non-numbers, ignore them.
**Agent (Phase 1 - Spec):**
> Thank you. I have created the specification file at `specs/stats-calculator.md` with the requirements we discussed. Now, I will start implementing it.
**Agent (Phase 2 - Build):**
> *(Creates `stats_calculator.js` and implements `mean`, `median`, and `mode` matching the spec exactly)*
> I have finished implementing the calculator. I will now verify the implementation.
**Agent (Phase 3 - Review):**
> Checking implementation against `specs/stats-calculator.md`:
> - Requirement: mean calculation -> PASS
> - Requirement: median calculation -> PASS
> - Requirement: mode calculation -> PASS
> - Requirement: empty array returns null -> PASS
> - Requirement: non-numbers are ignored -> FAIL (I noticed `mode` doesn't filter out non-numbers).
>
> I will now loop back to Phase 2 to fix the `mode` function.
**Agent (Phase 2 - Build Loop-Back):**
> *(Fixes `mode` to filter out non-numbers)*
> I have updated the implementation. Let's re-verify.
**Agent (Phase 3 - Review):**
> - Requirement: mode filters non-numbers -> PASS
>
> All requirements passed! The statistics calculator is complete and verified.
## Best Practices
- ✅ Do ask clarifying questions one at a time to avoid overwhelming the user during the planning phase.
- ✅ Do document edge cases explicitly in `specs/<feature-name>.md` before writing any code.
- ✅ Do stick strictly to the approved specification during the build phase.
- ✅ Do cap the loop with a small iteration budget and report exactly what remains if the budget is exhausted.
- ✅ Do pause for explicit approval before destructive, production, credentialed, or externally visible actions.
- ❌ Don't implement extra features or perform unrelated refactorings that aren't specified.
- ❌ Don't skip the review phase or pass it without verifying every single requirement.
- ❌ Don't keep retrying the same failing fix without new evidence or a changed approach.
## Limitations
- This skill requires sufficient context about the feature to be provided during the Spec phase.
- It is best suited for isolated features or tasks with clear boundaries, rather than open-ended architectural refactoring.
- The review phase relies on the agent's self-assessment against the generated spec; manual review is still recommended for critical systems.
- It is not a replacement for human approval on security-sensitive, destructive, production, compliance, or externally visible changes.
- It should stop rather than continue if requirements conflict, tests cannot run, or verification depends on unavailable credentials or systems.
## Security & Safety Notes
- Be cautious when running or testing code generated during the Build phase. Always run tests in a safe, sandboxed environment.
- Avoid executing arbitrary shell commands provided directly by the user without validating their safety.
- Make sure no hardcoded secrets, keys, or credentials are added to the code or specifications.
- Treat production deploys, data migrations, payment flows, credential changes, and external write actions as approval-gated work.
## Common Pitfalls
- **Problem:** The agent tries to build a huge system all at once, leading to an overcomplicated spec and incomplete implementation.
**Solution:** Keep the scope of `ai-loop` to small, modular features. Break larger systems into multiple independent loops.
- **Problem:** The spec is vague, causing the build phase to rely on assumptions.
**Solution:** Spend extra time in the planning phase asking targeted questions to pin down requirements.
## Related Skills
- `@plan-writing` - For writing more detailed implementation plans for larger projects.
- `@ask-questions-if-underspecified` - For standard guidelines on interviewing the user.
@@ -0,0 +1,244 @@
---
name: cron-doctor
description: "Diagnose and validate cron expressions before they ship. Catches the five silent death-traps: impossible dates that never fire, OR-semantics that fire too often, midnight spikes, uneven step drift, and leap-year February 29."
category: devops
risk: safe
source: community
source_repo: takeaseatventure/devops-skills
source_type: community
date_added: "2026-06-26"
author: takeaseat
tags: [cron, crontab, scheduling, devops, debugging, kubernetes, validation]
tools: [claude, cursor, codex, gemini, opencode]
license: "MIT"
license_source: "https://github.com/takeaseatventure/devops-skills/blob/main/LICENSE"
---
# cron-doctor
## Overview
Cron is deceptively error-prone. The failure mode is **silent** — a syntactically
valid expression that simply never fires, or fires far more often than intended.
`0 0 30 2 *` parses cleanly and then sits dead forever (February has no 30th).
`0 0 1,15 * 1` looks like "1st and 15th if Monday" but actually means "1st, 15th,
**OR** every Monday" — ~6 fires/month instead of ~2.
This skill teaches an agent to catch those before they reach production. It comes
with a zero-dependency validation engine (`scripts/cron-engine.js`, no install
needed) that parses, describes, deep-validates, and computes next fire times.
## When to Use This Skill
- Use when a user writes, edits, reviews, or deploys a cron expression — in a
crontab, a Kubernetes `CronJob`, a GitHub Actions `schedule`, an Airflow DAG,
a Celery beat schedule, a systemd timer, or any scheduled task.
- Use when debugging a job that "didn't fire" or "fired at the wrong time."
- Use when a user asks "what does this cron expression mean?" or "when will this
run next?" or "how often does this run per year?"
- Use when reviewing a CI/CD pipeline or infrastructure config that contains a
`schedule` field.
- Use when a user pastes a 5-field cron expression and asks for a sanity check.
## How It Works
### Step 1: Parse the expression
Split on whitespace into 5 fields: minute, hour, day-of-month, month, day-of-week.
Confirm valid ranges:
| Field | Position | Range | Notes |
|-------|----------|-------|-------|
| minute | 1 | 059 | |
| hour | 2 | 023 | |
| day-of-month | 3 | 131 | |
| month | 4 | 112 | names (JANDEC) accepted |
| day-of-week | 5 | 07 | 0 and 7 both = Sunday; names (SUNSAT) accepted |
### Step 2: Describe it in plain English
State what the user *thinks* it does vs. what it *actually* does. Be explicit
about OR-vs-AND semantics for day-of-month + day-of-week (see death-trap #2).
### Step 3: Run the trap checklist
Check the five death-traps below and flag any that apply.
### Step 4: Calculate next runs and annual fire count
Compute the next 5 fire times as concrete dates so the user can verify the
schedule behaves as expected. Estimate annual fire count — a schedule that fires
365×/year vs. 12×/year is a ~30× cost and load difference.
## The Five Cron Death-Traps
These are the bugs that pass `crontab -l` validation but break in production.
### 1. Impossible dates — the "never fires" bug
```
0 0 30 2 *
```
**Valid syntax. Never fires.** February has no 30th. This schedule is a dead job
that silently sits forever. The same applies to day 31 in any 30-day month:
`0 0 31 4 *`, `0 0 31 6 *`, `0 0 31 9 *`, `0 0 31 11 *`.
**Fix:** use `0 0 28-31 * *` and check for end-of-month in the script, or use `L`
(last day) syntax if your scheduler supports it.
### 2. OR-semantics — the "fires too often" bug
```
0 0 1,15 * 1
```
**Does NOT mean** "midnight on the 1st and 15th if it's Monday."
**Does mean** "midnight on the 1st, the 15th, **OR** every Monday." That's ~6
fires/month instead of ~2.
This is the single most misunderstood cron rule. When **both** day-of-month AND
day-of-week are restricted (neither is `*`), cron uses OR logic, not AND.
**Fix:** if you need "1st and 15th only if Monday," run daily and check in the
script:
```bash
0 0 * * 1 [ "$(date +%d)" = "01" -o "$(date +%d)" = "15" ] && your-command
```
### 3. Midnight spike — the "everything at once" bug
```
0 0 * * *
```
Every job scheduled at `0 0` competes for resources at exactly 00:00. Database
backups, log rotations, cert renewals, report generation — all fire simultaneously.
This causes load spikes, connection-pool exhaustion, and cascading timeouts.
**Fix:** stagger jobs across the hour. Use `17 2 * * *` or `43 3 * * *` instead of
`0 0`. Jitter is your friend.
### 4. Uneven steps — the "drift" bug
```
*/7 * * * *
```
**Does NOT mean** "every 7 minutes evenly." It means "every 7 minutes starting at
0, then resets at 60." So: 0, 7, 14, 21, 28, 35, 42, 49, 56 — then 0 again
(a 4-minute gap). The intervals drift: 7,7,7,7,7,7,7,7,**4**.
**Fix:** 60 is not divisible by 7. Use step values that divide 60 evenly: `*/5`,
`*/10`, `*/15`, `*/20`, `*/30`. If you truly need every-7-minutes, use a loop with
`sleep 420`.
### 5. Leap-year February 29 — the "annual surprise"
```
0 0 29 2 *
```
Fires only on leap years — February 29, 2024 / 2028 / 2032… If someone writes this
expecting "end of February," they'll be confused for 3 out of every 4 years.
**Fix:** use `0 0 28 2 *` and handle the 29th case in the script if needed.
## Using the validation script
This skill ships a zero-dependency engine at `scripts/cron-engine.js` (Node.js, no
`npm install` needed). You can use it programmatically or from the CLI:
```javascript
// Programmatic — Node.js, zero dependencies
const { describe, validate, nextRuns, formatNextRuns } = require('./scripts/cron-engine.js');
// Parse + describe -> returns { text, error, parsed }
const d = describe('0 0 30 2 *');
console.log(d.text); // "At 00:00, on day-of-month 30 in in FEB"
// Deep validation -> catches the traps
const result = validate('0 0 30 2 *');
console.log(result.valid); // true (syntax is valid)
console.log(result.observations); // includes the "never fires" insight
console.log(result.suggestions); // e.g. "Midnight is a common spike..."
// Next 5 fire times -> returns Date[]
const runs = nextRuns('0 9 * * 1-5', new Date(), 5);
console.log(formatNextRuns(runs, new Date())); // [{ date, relative, formatted }, ...]
```
```bash
# CLI (via the bundled wrapper)
node scripts/cli.js describe "*/5 * * * *"
node scripts/cli.js validate "0 0 30 2 *"
node scripts/cli.js next "0 9 * * 1-5" 5
```
## Common cron presets
| Expression | Description | Use case |
|-----------|-------------|----------|
| `*/5 * * * *` | Every 5 minutes | Health checks, polling |
| `0 * * * *` | Every hour | Hourly aggregation |
| `0 */2 * * *` | Every 2 hours | Semi-frequent sync |
| `0 9 * * 1-5` | 9am MonFri | Business-hours task |
| `0 2 * * *` | 2am daily | Off-peak batch (avoid midnight) |
| `0 0 * * 0` | Midnight Sunday | Weekly maintenance |
| `0 0 1 * *` | Midnight 1st of month | Monthly report |
| `0 0 1 1 *` | Midnight Jan 1st | Annual task |
## Best Practices
- ✅ Always provide the plain-English description AND run the trap checklist.
- ✅ Stagger midnight jobs to avoid the spike.
- ✅ Prefer step values that divide 60 evenly (`*/5`, `*/15`, `*/30`).
- ✅ Add a comment above every crontab line explaining intent.
- ✅ Set an explicit timezone (`CRON_TZ`) on schedulers that support it.
- ❌ Don't trust `crontab -l` validation — it only checks syntax, not semantics.
- ❌ Don't restrict both day-of-month and day-of-week without confirming OR-logic.
- ❌ Don't schedule everything at `0 0`.
## Common Pitfalls
- **Problem:** "My cron job isn't running."
**Solution:** Check for an impossible date (trap #1) and confirm the daemon is
running (`service cron status` / `systemctl status crond`). Verify the file
ends with a newline and has correct ownership.
- **Problem:** "My job runs far more often than expected."
**Solution:** You hit OR-semantics (trap #2). If both day-of-month and
day-of-week are set, cron ORs them. Move one to `*` or guard in-script.
- **Problem:** "Intervals are uneven — sometimes 7 min, sometimes 4."
**Solution:** Step value doesn't divide 60 evenly (trap #4). Use a divisor of 60.
- **Problem:** "My job works locally but not in the cluster."
**Solution:** Timezone mismatch. Kubernetes `CronJob` and GitHub Actions default
to UTC. Confirm `timeZone` / `TZ` is set as intended.
## Limitations
- This skill targets standard 5-field cron as implemented by Vixie cron, systemd
timers, Kubernetes `CronJob`, GitHub Actions `schedule`, and most libraries. It
does **not** validate Quartz 6/7-field expressions with seconds/years, nor
non-standard `@reboot` / `L` / `#` extensions without a note.
- Estimated annual fire counts assume a non-leap reference year; February 29
schedules (trap #5) are flagged explicitly.
- This skill does not replace environment-specific validation, testing, or expert
review. Stop and ask for clarification if required inputs, permissions, or
safety boundaries are missing.
## Related Skills
- `docker-expert` — when the cron job runs inside a container and the issue is the
container/entrypoint rather than the schedule.
- `kubernetes-deployment` — when validating a `CronJob` manifest's `spec.schedule`
field alongside the broader resource config.
## Security & Safety Notes
This skill is read-only and `risk: safe`. The validation script performs no file
writes, network calls, or mutations — it only parses and computes. It is safe to
run against any cron expression without preconditions.
@@ -0,0 +1,75 @@
#!/usr/bin/env node
'use strict';
// Minimal CLI wrapper for cron-engine.js. Zero dependencies.
// Usage:
// node cli.js describe "<cron>"
// node cli.js validate "<cron>"
// node cli.js next "<cron>" [count]
const cron = require('./cron-engine.js');
const expr = process.argv[3];
const cmd = process.argv[2];
if (!cmd || !expr) {
console.error('Usage: node cli.js <describe|validate|next> "<cron-expr>" [count]');
console.error('Examples:');
console.error(' node cli.js describe "*/5 * * * *"');
console.error(' node cli.js validate "0 0 30 2 *"');
console.error(' node cli.js next "0 9 * * 1-5" 5');
process.exit(2);
}
function safe(fn) {
try {
fn();
} catch (e) {
console.error('Error: ' + (e.message || e));
process.exit(1);
}
}
switch (cmd) {
case 'describe':
safe(() => {
const d = cron.describe(expr);
console.log(d.text || d.description || JSON.stringify(d));
});
break;
case 'validate':
safe(() => {
const r = cron.validate(expr);
console.log('valid: ' + r.valid);
if (r.description) console.log('description: ' + r.description);
if (r.warnings && r.warnings.length) {
console.log('warnings:');
r.warnings.forEach((w) => console.log(' - ' + w));
}
if (r.observations && r.observations.length) {
console.log('observations:');
r.observations.forEach((o) => console.log(' [' + (o.level || 'info') + '] ' + o.message));
}
if (r.suggestions && r.suggestions.length) {
console.log('suggestions:');
r.suggestions.forEach((s) => console.log(' [' + (s.level || 'info') + '] ' + s.message));
}
});
break;
case 'next':
safe(() => {
const count = parseInt(process.argv[4] || '5', 10);
const runs = cron.nextRuns(expr, new Date(), count);
const formatted = cron.formatNextRuns(runs, new Date());
formatted.forEach((f) =>
console.log(f.relative + '\t' + f.formatted + '\t' + f.date.toString())
);
});
break;
default:
console.error('Unknown command: ' + cmd);
console.error('Commands: describe, validate, next');
process.exit(2);
}
@@ -0,0 +1,638 @@
'use strict';
// ============================================================================
// cron.js — Cron expression parser, describer, validator, and next-run engine.
// Zero dependencies. Extracted from the DevRef Cron Expression Generator
// (battle-tested in browser) and extended with validate() for Pro insights.
// ============================================================================
const MONTH_NAMES = ['JAN','FEB','MAR','APR','MAY','JUN','JUL','AUG','SEP','OCT','NOV','DEC'];
const DAY_NAMES = ['SUN','MON','TUE','WED','THU','FRI','SAT'];
const FIELDS = [
{ name: 'minute', min: 0, max: 59, key: 'minute' },
{ name: 'hour', min: 0, max: 23, key: 'hour' },
{ name: 'dom', min: 1, max: 31, key: 'dom' },
{ name: 'month', min: 1, max: 12, key: 'month', named: MONTH_NAMES },
{ name: 'dow', min: 0, max: 7, key: 'dow', named: DAY_NAMES },
];
class CronError extends Error {
constructor(message, fieldIndex) {
super(message);
this.name = 'CronError';
this.fieldIndex = fieldIndex;
}
}
// ---- Name resolution ----
function resolveName(token, names) {
if (!names) return null;
const up = token.toUpperCase();
const idx = names.indexOf(up);
return idx === -1 ? null : idx;
}
// ---- Field parsing ----
function parseField(raw, fieldDef, fieldIndex) {
const trimmed = String(raw).trim();
if (trimmed === '') throw new CronError(`Field ${fieldIndex + 1} (${fieldDef.name}) is empty`, fieldIndex);
const out = { raw: trimmed, values: null, special: null };
// Special: day-of-week "#" (nth weekday)
if (fieldDef.key === 'dow' && trimmed.includes('#')) {
const m = trimmed.match(/^([0-7A-Za-z]+)#([1-5])$/);
if (!m) throw new CronError(`Invalid "#" syntax in day-of-week: "${trimmed}"`, fieldIndex);
let dowNum = parseSingleNum(m[1], fieldDef, fieldIndex);
if (dowNum === 7) dowNum = 0;
out.special = { kind: 'hash', dow: dowNum, nth: parseInt(m[2], 10) };
return out;
}
// Special: day-of-week "L" (last weekday)
if (fieldDef.key === 'dow' && /L$/i.test(trimmed)) {
const m = trimmed.match(/^([0-7A-Za-z]+)L$/i);
if (!m) throw new CronError(`Invalid "L" syntax in day-of-week: "${trimmed}"`, fieldIndex);
let dowNum = parseSingleNum(m[1], fieldDef, fieldIndex);
if (dowNum === 7) dowNum = 0;
out.special = { kind: 'dowLast', dow: dowNum };
return out;
}
// Special: day-of-month "L" (last day)
if (fieldDef.key === 'dom' && /^L/i.test(trimmed)) {
const m = trimmed.match(/^L(?:-(\d+))?$/i);
if (!m) throw new CronError(`Invalid "L" syntax in day-of-month: "${trimmed}"`, fieldIndex);
out.special = { kind: 'domLast', offset: m[1] ? parseInt(m[1], 10) : 0 };
return out;
}
// Special: day-of-month "W" (nearest weekday)
if (fieldDef.key === 'dom' && /W$/i.test(trimmed)) {
const m = trimmed.match(/^(\d+)W$/i);
if (!m) throw new CronError(`Invalid "W" syntax in day-of-month: "${trimmed}"`, fieldIndex);
const day = parseInt(m[1], 10);
if (day < fieldDef.min || day > fieldDef.max) {
throw new CronError(`Day-of-month "${day}W" out of range (${fieldDef.min}-${fieldDef.max})`, fieldIndex);
}
out.special = { kind: 'weekday', day: day };
return out;
}
// Standard parsing
const values = new Set();
const items = trimmed.split(',');
for (const item of items) {
parseItem(item, fieldDef, fieldIndex, values);
}
out.values = values;
return out;
}
function parseSingleNum(token, fieldDef, fieldIndex) {
const n = parseInt(token, 10);
if (!isNaN(n)) return n;
const named = resolveName(token, fieldDef.named);
if (named !== null) {
return fieldDef.key === 'month' ? named + 1 : named;
}
throw new CronError(`Invalid value "${token}" in ${fieldDef.name}`, fieldIndex);
}
function parseItem(item, fieldDef, fieldIndex, values) {
const t = item.trim();
if (t === '') throw new CronError(`Empty item in ${fieldDef.name}`, fieldIndex);
if (t === '*') {
addRange(values, fieldDef.min, fieldDef.max, fieldDef);
return;
}
if (t.includes('/')) {
const [base, stepStr] = t.split('/');
const step = parseInt(stepStr, 10);
if (isNaN(step) || step < 1) throw new CronError(`Invalid step "${stepStr}" in ${fieldDef.name}`, fieldIndex);
let lo, hi;
if (base === '*' || base === '') {
lo = fieldDef.min; hi = fieldDef.max;
} else if (base.includes('-')) {
const [a, b] = base.split('-');
lo = parseSingleNum(a.trim(), fieldDef, fieldIndex);
hi = parseSingleNum(b.trim(), fieldDef, fieldIndex);
} else {
lo = parseSingleNum(base.trim(), fieldDef, fieldIndex);
hi = fieldDef.max;
}
if (lo > hi) [lo, hi] = [hi, lo];
for (let v = lo; v <= hi; v += step) addOne(values, v, fieldDef, fieldIndex);
return;
}
if (t.includes('-')) {
const parts = t.split('-');
if (parts.length !== 2) throw new CronError(`Invalid range "${t}" in ${fieldDef.name}`, fieldIndex);
const a = parseSingleNum(parts[0].trim(), fieldDef, fieldIndex);
const b = parseSingleNum(parts[1].trim(), fieldDef, fieldIndex);
addRange(values, a, b, fieldDef);
return;
}
const v = parseSingleNum(t, fieldDef, fieldIndex);
addOne(values, v, fieldDef, fieldIndex);
}
function addOne(values, v, fieldDef, fieldIndex) {
if (fieldDef.key === 'dow' && v === 7) { values.add(0); return; }
if (v < fieldDef.min || v > fieldDef.max) {
throw new CronError(`Value ${v} out of range for ${fieldDef.name} (${fieldDef.min}-${fieldDef.max})`, fieldIndex);
}
values.add(v);
}
function addRange(values, lo, hi, fieldDef) {
if (lo > hi) [lo, hi] = [hi, lo];
if (lo < fieldDef.min || hi > fieldDef.max) {
throw new CronError(`Range ${lo}-${hi} out of bounds for ${fieldDef.name} (${fieldDef.min}-${fieldDef.max})`, -1);
}
for (let v = lo; v <= hi; v++) {
if (fieldDef.key === 'dow' && v === 7) { values.add(0); continue; }
values.add(v);
}
}
// ---- Full expression parser ----
function parseCron(expr) {
const parts = String(expr).trim().split(/\s+/);
if (parts.length !== 5) {
throw new CronError(`Expected 5 fields (got ${parts.length}). Format: minute hour day-of-month month day-of-week`, -1);
}
const parsed = {};
for (let i = 0; i < 5; i++) {
parsed[FIELDS[i].key] = parseField(parts[i], FIELDS[i], i);
}
parsed.domRestricted = !/^\s*\*\s*$/.test(parts[2]);
parsed.dowRestricted = !/^\s*\*\s*$/.test(parts[4]);
parsed.parts = parts;
return parsed;
}
// ---- Human-readable description ----
function describe(expr) {
let parsed;
try { parsed = parseCron(expr); } catch (e) { return { text: e.message, error: true }; }
return { text: describeParsed(parsed), error: false, parsed };
}
function describeParsed(p) {
const monthDesc = describeFieldMonth(p.month);
const domDesc = describeFieldDom(p.dom);
const dowDesc = describeFieldDow(p.dow);
const isEveryMin = p.parts[0] === '*';
const isEveryHour = p.parts[1] === '*';
let timePart = '';
if (isEveryMin && isEveryHour) {
timePart = 'At every minute';
} else if (isEveryMin && !isEveryHour) {
const hours = [...(p.hour.values || [])].sort((a, b) => a - b);
if (hours.length > 0) {
timePart = 'Every minute during the ' + hours.map(h => pad2(h)).join(', ') + ' hour' + (hours.length > 1 ? 's' : '');
} else {
timePart = 'Every minute';
}
} else {
timePart = 'At ' + describeTimes(p.minute, p.hour);
}
let dayPart = '';
const domAny = !p.domRestricted;
const dowAny = !p.dowRestricted;
if (domAny && dowAny) {
if (monthDesc.restricted) {
dayPart = ', ' + monthDesc.text + ' of every year';
} else {
dayPart = ', every day';
}
} else if (!domAny && dowAny) {
dayPart = ', on ' + domDesc.text;
if (monthDesc.restricted) dayPart += ' in ' + monthDesc.text;
} else if (domAny && !dowAny) {
dayPart = ', on ' + dowDesc.text;
if (monthDesc.restricted) dayPart += ' in ' + monthDesc.text;
} else {
dayPart = ', on ' + domDesc.text + ' and on ' + dowDesc.text;
if (monthDesc.restricted) dayPart += ' in ' + monthDesc.text;
}
return capitalize(timePart + dayPart);
}
function describeTimes(minuteField, hourField) {
const mins = [...(minuteField.values || [])].sort((a, b) => a - b);
const hours = [...(hourField.values || [])].sort((a, b) => a - b);
if (pIsWildcard(hourField) && !pIsWildcard(minuteField)) {
if (mins.length === 1) return `minute ${mins[0]} of every hour`;
return `minutes ${listJoin(mins)} of every hour`;
}
if (pIsWildcard(minuteField) && pIsWildcard(hourField)) return 'every minute of every hour';
if (pIsWildcard(minuteField)) {
return `every minute during the ${hours.map(h => pad2(h)).join(', ')} hour${hours.length > 1 ? 's' : ''}`;
}
const combos = [];
for (const h of hours) {
for (const m of mins) {
combos.push(formatHM(h, m));
}
}
return listJoin(combos);
}
function describeFieldMonth(field) {
if (pIsWildcard(field)) return { restricted: false, text: 'every month' };
const vals = [...(field.values || [])].sort((a, b) => a - b);
return { restricted: true, text: 'in ' + listJoin(vals.map(v => capitalize(MONTH_NAMES[v - 1]))) };
}
function describeFieldDom(field) {
if (pIsWildcard(field)) return { text: 'every day-of-month' };
if (field.special) {
if (field.special.kind === 'domLast') {
return { text: field.special.offset === 0 ? 'the last day of the month' : `the last day of the month minus ${field.special.offset} days` };
}
if (field.special.kind === 'weekday') {
return { text: `the nearest weekday to day ${field.special.day}` };
}
}
const vals = [...(field.values || [])].sort((a, b) => a - b);
return { text: `day-of-month ${listJoin(vals)}` };
}
function describeFieldDow(field) {
if (pIsWildcard(field)) return { text: 'every day-of-week' };
if (field.special) {
if (field.special.kind === 'hash') {
return { text: `the ${ordinal(field.special.nth)} ${capitalize(DAY_NAMES[field.special.dow])} of the month` };
}
if (field.special.kind === 'dowLast') {
return { text: `the last ${capitalize(DAY_NAMES[field.special.dow])} of the month` };
}
}
const vals = [...(field.values || [])].sort((a, b) => a - b);
return { text: listJoin(vals.map(v => capitalize(DAY_NAMES[v]))) };
}
function pIsWildcard(field) { return field.raw === '*'; }
// ---- Next run calculator ----
function nextRuns(expr, fromDate, count) {
count = count || 10;
const p = parseCron(expr);
const runs = [];
let d = new Date(fromDate.getTime());
d.setSeconds(0, 0);
d = new Date(d.getTime() + 60000);
let maxScan = 600000; // ~416 days ceiling
while (runs.length < count && maxScan-- > 0) {
if (matches(d, p)) {
runs.push(new Date(d.getTime()));
}
d = new Date(d.getTime() + 60000);
}
return runs;
}
function matches(d, p) {
if (!p.minute.values || !p.minute.values.has(d.getMinutes())) return false;
if (!p.hour.values || !p.hour.values.has(d.getHours())) return false;
if (!p.month.values || !p.month.values.has(d.getMonth() + 1)) return false;
const domAny = !p.domRestricted;
const dowAny = !p.dowRestricted;
let domMatch = false, dowMatch = false;
if (domAny) {
domMatch = true;
} else if (p.dom.special) {
domMatch = matchDomSpecial(d, p.dom.special);
} else if (p.dom.values && p.dom.values.has(d.getDate())) {
domMatch = true;
}
if (dowAny) {
dowMatch = true;
} else if (p.dow.special) {
dowMatch = matchDowSpecial(d, p.dow.special);
} else if (p.dow.values) {
dowMatch = p.dow.values.has(d.getDay());
}
if (domAny && dowAny) return true;
if (!domAny && !dowAny) return domMatch || dowMatch; // OR semantics
return domMatch && dowMatch;
}
function matchDomSpecial(d, special) {
if (special.kind === 'domLast') {
const lastDay = lastDayOfMonth(d.getFullYear(), d.getMonth());
const target = special.offset === 0 ? lastDay : lastDay - special.offset;
return d.getDate() === target;
}
if (special.kind === 'weekday') {
return d.getDate() === nearestWeekday(d.getFullYear(), d.getMonth(), special.day);
}
return false;
}
function matchDowSpecial(d, special) {
if (special.kind === 'hash') {
return nthWeekdayMatches(d, special.dow, special.nth);
}
if (special.kind === 'dowLast') {
return lastWeekdayMatches(d, special.dow);
}
return false;
}
function nthWeekdayMatches(d, dow, nth) {
if (d.getDay() !== dow) return false;
const dayOfMonth = d.getDate();
const occurrence = Math.ceil(dayOfMonth / 7);
return occurrence === nth;
}
function lastWeekdayMatches(d, dow) {
if (d.getDay() !== dow) return false;
const lastDay = lastDayOfMonth(d.getFullYear(), d.getMonth());
return d.getDate() + 7 > lastDay;
}
function lastDayOfMonth(year, month) {
return new Date(year, month + 1, 0).getDate();
}
function nearestWeekday(year, month, day) {
const lastDay = lastDayOfMonth(year, month);
const target = Math.min(day, lastDay);
const dt = new Date(year, month, target);
const wd = dt.getDay();
let result = target;
if (wd === 0) {
if (target + 1 <= lastDay) result = target + 1;
else result = target - 2;
} else if (wd === 6) {
if (target - 1 >= 1) result = target - 1;
else result = target + 2;
}
return result;
}
// ============================================================================
// validate() — Pro-tier feature: deeper analysis of a cron expression.
// Returns warnings, observations, and optimization suggestions.
// ============================================================================
function validate(expr) {
let parsed;
try {
parsed = parseCron(expr);
} catch (e) {
return {
valid: false,
error: e.message,
fieldIndex: e.fieldIndex,
warnings: [],
observations: [],
suggestions: [],
};
}
const warnings = [];
const observations = [];
const suggestions = [];
const desc = describeParsed(parsed);
// Check: day-of-month and day-of-week both restricted (OR semantics surprise)
if (parsed.domRestricted && parsed.dowRestricted) {
warnings.push({
level: 'high',
message: 'Both day-of-month and day-of-week are restricted. Cron uses OR semantics for these fields — the job will run when EITHER matches, not both. This is a common source of bugs.',
});
}
// Check: impossible day-of-month values (e.g., 31 in Feb)
const domValues = [...(parsed.dom.values || [])];
if (!parsed.domRestricted && parsed.month.values && ![...parsed.month.values].every(m => m === 2)) {
// skip
} else if (parsed.domRestricted && !parsed.dom.special && domValues.includes(31)) {
const monthsWith31 = [1, 3, 5, 7, 8, 10, 12]; // Jan, Mar, May, Jul, Aug, Oct, Dec
const monthValues = parsed.month.values ? [...parsed.month.values] : [];
const restrictedMonths = parsed.parts[3] !== '*';
if (restrictedMonths) {
const problemMonths = monthValues.filter(m => !monthsWith31.includes(m));
if (problemMonths.length > 0) {
warnings.push({
level: 'medium',
message: `Day 31 is specified but months ${problemMonths.map(m => capitalize(MONTH_NAMES[m - 1])).join(', ')} have fewer than 31 days. The job will never run in those months.`,
});
}
} else {
observations.push({
level: 'info',
message: 'Day 31 will only match in months with 31 days (7 of 12 months). The job effectively skips Feb, Apr, Jun, Sep, and Nov.',
});
}
}
// Check: high-frequency schedules
if (parsed.parts[0] === '*' && parsed.parts[1] === '*') {
observations.push({
level: 'info',
message: 'This expression runs every minute. For production jobs, consider if this frequency is intentional.',
});
}
// Check: step values that don't divide evenly
for (let i = 0; i < 2; i++) {
const part = parsed.parts[i];
if (part.startsWith('*/')) {
const step = parseInt(part.slice(2), 10);
const range = i === 0 ? 60 : 24;
if (range % step !== 0) {
observations.push({
level: 'info',
message: `Step value */${step} in ${FIELDS[i].name} doesn't divide evenly into ${range}. The last interval will be shorter than the rest (e.g., */7 in minutes goes 0,7,14,...,56, then 0 again — not 63).`,
});
}
}
}
// Check: February 29th edge case
if (parsed.domRestricted && !parsed.dom.special) {
const domVals = [...(parsed.dom.values || [])];
const monthVals = parsed.month.values ? [...parsed.month.values] : [];
if (domVals.includes(29) && monthVals.length === 1 && monthVals[0] === 2) {
warnings.push({
level: 'medium',
message: 'February 29th only occurs in leap years. This job will not run at all in non-leap years (3 out of every 4 years).',
});
}
}
// Check: midnight rush
if (parsed.parts[0] === '0' && parsed.parts[1] === '0') {
suggestions.push({
level: 'info',
message: 'Midnight (00:00) is a common schedule and many systems have concurrent job spikes at this time. Consider offsetting to a few minutes past midnight (e.g., 02 0 * * *) to avoid resource contention.',
});
}
// Check: weekend vs weekday
if (parsed.parts[4] === '1-5') {
observations.push({
level: 'info',
message: 'Weekdays only (Mon-Fri). This job will not run on weekends.',
});
}
// Compute frequency estimate
const freq = estimateFrequency(parsed);
if (freq) {
observations.push({
level: 'info',
message: `Approximate frequency: ${freq.description} (~${freq.runsPerYear} runs per year).`,
});
}
return {
valid: true,
description: desc,
warnings,
observations,
suggestions,
parsed,
};
}
function estimateFrequency(parsed) {
try {
// Count runs over a sample year
const start = new Date(2025, 0, 1, 0, 0, 0, 0);
const end = new Date(2026, 0, 1, 0, 0, 0, 0);
let count = 0;
let d = new Date(start.getTime());
let maxScan = 540000; // ~375 days
while (d < end && maxScan-- > 0) {
if (matches(d, parsed)) count++;
d = new Date(d.getTime() + 60000);
}
let description = '';
if (count >= 525600) description = 'every minute';
else if (count >= 500000) description = 'multiple times per minute';
else if (count >= 8000) description = 'hourly or more';
else if (count >= 300) description = 'daily or more';
else if (count >= 40) description = 'weekly or more';
else if (count >= 8) description = 'monthly or more';
else if (count >= 1) description = 'yearly or less';
else description = 'never (impossible schedule)';
return { description, runsPerYear: count };
} catch (e) {
return null;
}
}
// ---- Presets ----
const PRESETS = [
{ label: 'Every minute', cron: '* * * * *' },
{ label: 'Every 5 min', cron: '*/5 * * * *' },
{ label: 'Every 10 min', cron: '*/10 * * * *' },
{ label: 'Every 15 min', cron: '*/15 * * * *' },
{ label: 'Every 30 min', cron: '*/30 * * * *' },
{ label: 'Hourly', cron: '0 * * * *' },
{ label: 'Every 2 hours', cron: '0 */2 * * *' },
{ label: 'Every 6 hours', cron: '0 */6 * * *' },
{ label: 'Every 12 hours', cron: '0 */12 * * *' },
{ label: 'Daily at midnight', cron: '0 0 * * *' },
{ label: 'Daily 9am', cron: '0 9 * * *' },
{ label: 'Twice daily', cron: '0 9,21 * * *' },
{ label: 'Weekdays 9am', cron: '0 9 * * 1-5' },
{ label: 'Weekends 10am', cron: '0 10 * * 0,6' },
{ label: 'Every Monday', cron: '0 0 * * 1' },
{ label: 'Every Friday', cron: '0 0 * * 5' },
{ label: 'Monthly 1st', cron: '0 0 1 * *' },
{ label: 'Quarterly', cron: '0 0 1 */3 *' },
{ label: 'Yearly Jan 1', cron: '0 0 1 1 *' },
];
const COMMON = [
{ label: 'At 14:30', cron: '30 14 * * *' },
{ label: '9am weekdays', cron: '0 9 * * 1-5' },
{ label: 'Every Mon 8am', cron: '0 8 * * 1' },
{ label: 'Last day of month', cron: '0 0 L * *' },
{ label: '15th, weekday', cron: '0 0 15W * *' },
{ label: '3rd Thursday', cron: '0 0 * * 4#3' },
{ label: 'Last Friday', cron: '0 0 * * 5L' },
{ label: 'Business hours', cron: '0 9-17 * * 1-5' },
{ label: 'Backup nightly', cron: '0 2 * * *' },
];
// ---- Helpers ----
function pad2(n) { return String(n).padStart(2, '0'); }
function formatHM(h, m) { return `${pad2(h)}:${pad2(m)}`; }
function capitalize(s) { return s.charAt(0).toUpperCase() + s.slice(1); }
function ordinal(n) {
const s = ['th', 'st', 'nd', 'rd'];
const v = n % 100;
return n + (s[(v - 20) % 10] || s[v] || s[0]);
}
function listJoin(arr) {
if (arr.length === 0) return '';
if (arr.length === 1) return String(arr[0]);
if (arr.length === 2) return `${arr[0]} and ${arr[1]}`;
return arr.slice(0, -1).join(', ') + ', and ' + arr[arr.length - 1];
}
function formatNextRuns(runs, fromDate) {
return runs.map(r => {
const diff = r.getTime() - fromDate.getTime();
const mins = Math.round(diff / 60000);
let rel;
if (mins < 60) rel = `+${mins}m`;
else if (mins < 2880) rel = `+${Math.round(mins / 60)}h`;
else rel = `+${Math.round(mins / 1440)}d`;
return { date: r, relative: rel, formatted: r.toISOString() };
});
}
module.exports = {
CronError,
FIELDS,
MONTH_NAMES,
DAY_NAMES,
PRESETS,
COMMON,
parseCron,
describe,
describeParsed,
nextRuns,
matches,
validate,
estimateFrequency,
formatNextRuns,
parseField,
parseItem,
parseSingleNum,
resolveName,
lastDayOfMonth,
nearestWeekday,
nthWeekdayMatches,
lastWeekdayMatches,
};
@@ -0,0 +1,131 @@
---
name: sql-sentinel
description: "Audit SQL for the cost & performance anti-patterns that burn warehouse credits. Scores warehouse health 0-100 and outputs a prioritized cost-reduction plan for BigQuery, Snowflake, Redshift, and Postgres."
category: data
risk: safe
source: community
source_repo: takeaseatventure/sql-sentinel
source_type: community
date_added: "2026-06-26"
author: takeaseat
tags: [sql, bigquery, snowflake, redshift, postgres, data-warehouse, cost-optimization, performance, audit, finops]
tools: [claude, cursor, codex, gemini]
license: "MIT"
license_source: "https://github.com/takeaseatventure/sql-sentinel/blob/main/LICENSE"
---
# sql-sentinel
## Overview
A static-analysis skill that audits SQL for the cost & performance anti-patterns that dominate warehouse bills — `SELECT *`, full-table scans, non-sargable predicates, Cartesian joins, the `NOT IN` NULL trap, and 15 more. It scores warehouse query health 0-100 (A-F) and outputs a prioritized cost-reduction plan, each finding with a `why`, a concrete `fix`, and an estimated savings.
Built for analytics engineers (dbt, Looker), data platform teams running FinOps / "reduce cloud spend" initiatives, and anyone reviewing a SQL pull request before it hits production. Works across BigQuery, Snowflake, Redshift, and Postgres. Zero dependencies, MIT licensed.
The executable engine and full rule set live in the source repository: https://github.com/takeaseatventure/sql-sentinel
## When to Use This Skill
- A user writes or reviews a query for BigQuery, Snowflake, Redshift, Postgres, or Spark SQL.
- A user asks "why is this query so slow?" or "why is my warehouse bill so high?"
- A user is about to promote a dashboard query or dbt model to production.
- A data engineer wants a second pair of eyes before a code review or a cost-optimization sweep.
- A team is running a "reduce cloud spend" or FinOps initiative.
## How It Works
The engine splits a SQL script into statements (honoring quotes and comments), runs 20 rules over each statement, scores health 0-100 weighted by severity (critical 25, high 12, medium 5, low 1), and returns a prioritized cost-reduction plan.
### Step 1: Run the audit
Install or clone the source repository, then run the zero-dependency engine:
```bash
git clone https://github.com/takeaseatventure/sql-sentinel.git
cd sql-sentinel
node scripts/sql-sentinel.js path/to/query.sql
```
Or programmatically:
```javascript
const { auditSql } = require('./scripts/sql-sentinel');
const report = auditSql(yourSqlString, { dialect: 'bigquery' });
console.log(report.healthScore); // 0-100
console.log(report.grade); // 'A' | 'B' | 'C' | 'D' | 'E' | 'F'
console.log(report.prioritizedPlan); // array, worst findings first
```
### Step 2: Read the prioritized plan
The output leads with critical findings (Cartesian joins, mass DELETE) and descends to low-severity style issues. Each finding explains *why* it costs money and *how* to fix it.
## Examples
### Example 1: A messy dashboard query
```sql
SELECT DISTINCT *
FROM user_events, raw_logs
WHERE LOWER(event_name) LIKE '%signup%'
AND user_id NOT IN (SELECT id FROM deleted_users)
ORDER BY created_at;
```
The audit scores this 17/100 (grade F) and flags 7 findings:
- CRITICAL: comma-join produces a Cartesian product (can turn a $0.02 query into a $200 query)
- HIGH: `SELECT *` forces full column scan (30-90% wasted bytes on wide tables)
- HIGH: leading-wildcard `LIKE '%signup%'` defeats indexes
- HIGH: `LOWER(event_name)` defeats indexes (non-sargable)
- HIGH: `NOT IN (SELECT ...)` — NULL semantics hazard
- MEDIUM: `SELECT DISTINCT` dedup cost
- MEDIUM: `ORDER BY` without `LIMIT` sorts the full result
### Example 2: A clean, sargable query
```sql
-- This scores 90+/100 (grade A) — no findings
SELECT id, email, created_at
FROM users
WHERE created_at >= TIMESTAMP '2026-01-01'
AND created_at < TIMESTAMP '2026-02-01'
ORDER BY id
LIMIT 100;
```
## The 20 rules (ruleset v1.0.0)
| Rule | Severity | Catches |
|---|---|---|
| SQL001 | high | `SELECT *` full column scan |
| SQL002 | critical | No `WHERE` → full table scan |
| SQL003 | high | `LIKE '%term'` non-sargable |
| SQL004 | high | Function on column kills index |
| SQL005 | critical | `CROSS JOIN` / comma-join |
| SQL006 | medium | `SELECT DISTINCT` dedup cost |
| SQL007 | medium | `ORDER BY` without `LIMIT` |
| SQL008 | high | `NOT IN (SELECT ...)` NULL trap |
| SQL009 | medium | Implicit type cast |
| SQL010 | low | Many `OR`s (use `IN`/`UNION`) |
| SQL011 | medium | `COUNT(DISTINCT)` at scale (use HLL) |
| SQL012 | low | `LIMIT` without `ORDER BY` |
| SQL013 | medium | Scalar subquery in `SELECT` |
| SQL014 | medium | 5+ JOINs broadcast/spill risk |
| SQL015 | high | Fact table, no partition filter |
| SQL017 | low | String concat in `SELECT` |
| SQL018 | medium | Window `OVER ()` no `PARTITION` |
| SQL020 | critical | `DELETE`/`UPDATE` without `WHERE` |
| SQL021 | low | `SELECT *` in `EXISTS`/`IN` |
| SQL022 | medium | `UNION` vs `UNION ALL` |
Run the test suite to verify each rule fires on real SQL:
```bash
cd scripts && node test.js # 26 tests, zero dependencies
```
## Limitations
- This is a **static** analyzer. It finds anti-patterns in the *text* of SQL; it does not read query plans, row counts, or billing. A flagged query on a 100-row table is cheap; the same query on a billion-row table is the problem the rule exists to prevent.
- The fact-table heuristic (SQL015) keys off table *names* (`*_events`, `*_log`) and is advisory, not definitive.
- It does not execute SQL — safe to run on any `.sql` file.
@@ -0,0 +1,149 @@
---
name: web-project-brainstorming
description: Masterclass framework for brainstorming web development projects and page designs. Outlines structural phases for concept, UX flow, styling aesthetics, technical architecture, and SEO.
category: consulting
risk: safe
source: self
source_type: self
date_added: "2026-06-26"
author: Rsmiyani
tags: [brainstorming, project-planning, web-development, product-scoping, design-system, architecture]
tools: [claude, cursor, gemini]
---
# Web Project Brainstorming
## Overview
This skill provides a structured, masterclass-level framework for brainstorming web projects, web applications, or individual page designs at their inception. It guides developers and designers through scoping the core product concept, mapping user flows, defining visual styling aesthetics, selecting the technical stack, and planning for search engine optimization (SEO) and performance.
## When to Use This Skill
- Use at the start of any new web development project or page redesign.
- Use when scoping feature sets, user roles, and interaction patterns for web applications.
- Use when establishing design systems, color tokens, and layout guidelines.
- Use when evaluating tech stacks (e.g., Next.js vs. Vanilla JS, CSS Grid vs. Tailwind).
## How It Works
Execute web project brainstorming sequentially across six structured phases. Ask the user questions one phase at a time to maintain focus and ensure thorough alignment.
### Phase 1: Core Concept & Scoping
Define the product's primary value proposition and scope:
- **Target Audience**: Who is using the website or application?
- **Core Value**: What problem does it solve for users?
- **Key Features**: What are the top 35 mandatory features?
### Phase 2: User Experience (UX) & Information Architecture
Map how users navigate and interact:
- **Page Hierarchy**: What is the sitemap and page structure?
- **User Journeys**: What step-by-step flows do users take to complete key goals?
- **Responsive Layout**: Is the interface mobile-first, desktop-first, or balanced?
### Phase 3: Visual Styling & Design System
Establish the visual guidelines and aesthetic parameters:
- **Design Aesthetic**: Modern, minimalist, brutalist, glassmorphism, or luxury?
- **Color Palette**: What are the primary, secondary, and accent colors? (Prefer tailorable HSL/RGB models over static color keywords).
- **Typography**: Which Google Fonts or system fonts fit the theme? (e.g., Inter, Outfit, Syne).
- **Interactive States**: How do hovers, clicks, transitions, and loading states behave?
### Phase 4: Technical Stack & Architecture
Select the technologies and integration systems:
- **Frontend Framework**: React, Next.js, Vite, Astro, Svelte, or Vanilla HTML/JS?
- **Styling Method**: Vanilla CSS, Tailwind CSS, or CSS Modules?
- **Data & Backend**: REST API, GraphQL, tRPC, Firebase, Supabase, or SQLite?
- **State Management**: Zustand, Context API, Redux, or local React state?
### Phase 5: SEO, Accessibility (A11y), and Performance
Plan for discoverability and fast loading times:
- **SEO Elements**: Title tag structure, meta descriptions, and semantic HTML tag hierarchy.
- **Accessibility**: ARIA labels, semantic tags, keyboard navigation, and color contrast.
- **Performance**: Preloading assets, lazy loading images, server-side rendering (SSR), and CDN delivery.
### Phase 6: MVP Scope & Project Phases
Break the work down into manageable increments:
- **Phase 1 (MVP)**: The absolute minimum viable product needed to deploy.
- **Phase 2 (Enhancements)**: Nice-to-have features, micro-animations, and advanced integrations.
## Examples
### Interactive Questionnaire Prompt Template
Use this prompt layout when initiating a brainstorming session with a client or team member:
```markdown
👋 Let's brainstorm your new web project! We will walk through 6 quick phases.
---
### Phase 1: Core Concept & Scoping
1. What is the main title or working name of this project?
2. Who are the primary target users (e.g., tech-savvy professionals, shoppers, children)?
3. What are the 3 core tasks a user must be able to perform?
---
```
### Brainstorming Output Document Template
Once all phases are complete, generate a markdown blueprint for the project using this template:
```markdown
# Project Blueprint: [Project Name]
## 1. Product Concept
- **Value Proposition**: [Summary]
- **Key Features**:
1. [Feature 1]
2. [Feature 2]
## 2. Information Architecture & UX
- **Pages**: `/index.html`, `/dashboard.html`
- **Primary User Flow**: User signs up -> completes onboarding -> views dashboard.
## 3. Styling & Aesthetics
- **Aesthetic**: Sleek Glassmorphism Dark Mode
- **Color Tokens**:
- Background: `hsl(222, 47%, 11%)`
- Accent/Primary: `hsl(217, 91%, 60%)`
- **Typography**: Inter (Body), Outfit (Headings)
## 4. Technical Architecture
- **Framework**: Next.js (App Router)
- **Styling**: Tailwind CSS
- **Database**: PostgreSQL with Prisma ORM
## 5. SEO & Performance
- **Primary Title**: "[Brand] | [Tagline]"
- **Performance Strategy**: Dynamic image optimization, caching pages via Cloudflare.
## 6. MVP vs Phase 2 Roadmap
- **MVP**: Authentication + core dashboard view.
- **Phase 2**: Real-time notifications and PDF reporting.
```
## Best Practices
- ✅ Ask questions incrementally—never dump all six phases in a single response to avoid cognitive overload.
- ✅ Propose logical defaults (e.g., recommending responsive Tailwind/CSS Grid and standard semantic HTML) if the user is unsure.
- ✅ Ensure semantic HTML layout hierarchy (one `<h1>` per page, sequential `<section>`, `<article>`, `<header>`, `<footer>` elements) is planned from the start.
- ✅ Document explicit non-goals to prevent feature creep.
## Limitations
- This skill focuses on conceptual mapping, architecture, and feature planning; it does not replace the writing of implementation code or system configuration.
- Brainstorming outcomes should be treated as flexible blueprints and refined as technical constraints are discovered during development.
## Security & Safety Notes
- During Phase 4 (Architecture), flag any security requirements (e.g., SSL certificates, CORS policies, secure authentication storage, environment variables protection) early.
- Do not store actual API tokens, passwords, or credentials in design or blueprint documents.
## Common Pitfalls
- **Problem**: Scope Creep (the project expands too quickly before building an MVP).
**Solution**: Enforce Phase 6 strictly. Push nice-to-have features into Phase 2.
- **Problem**: Ignoring mobile design until late in development.
**Solution**: Brainstorm responsive patterns in Phase 2 before deciding on layout style in Phase 3.
## Related Skills
- `@writing-plans` - Organizing structural step-by-step engineering plans.
- `@architecture-decision-records` - Documenting architectural decisions.
- `@ux-flow` - Designing deep user experience flows and interaction details.
@@ -1,6 +1,6 @@
{
"name": "antigravity-bundle-aas-accessibility-inclusive-ux",
"version": "13.3.0",
"version": "13.4.0",
"description": "Editorial \"AAS Accessibility & Inclusive UX\" bundle for Claude Code from Antigravity Awesome Skills.",
"author": {
"name": "sickn33 and contributors",
@@ -1,6 +1,6 @@
{
"name": "agyb-aas-accessibility-inclusive-ux",
"version": "13.3.0",
"version": "13.4.0",
"description": "Install the \"AAS Accessibility & Inclusive UX\" workflow plugin from Antigravity Awesome Skills.",
"author": {
"name": "sickn33 and contributors",
@@ -1,6 +1,6 @@
{
"name": "antigravity-bundle-aas-agent-mcp-builder",
"version": "13.3.0",
"version": "13.4.0",
"description": "Editorial \"AAS Agent & MCP Builder\" bundle for Claude Code from Antigravity Awesome Skills.",
"author": {
"name": "sickn33 and contributors",
@@ -1,6 +1,6 @@
{
"name": "agyb-aas-agent-mcp-builder",
"version": "13.3.0",
"version": "13.4.0",
"description": "Install the \"AAS Agent & MCP Builder\" workflow plugin from Antigravity Awesome Skills.",
"author": {
"name": "sickn33 and contributors",
@@ -1,6 +1,6 @@
{
"name": "antigravity-bundle-aas-ai-product-evaluation-ops",
"version": "13.3.0",
"version": "13.4.0",
"description": "Editorial \"AAS AI Product & Evaluation Ops\" bundle for Claude Code from Antigravity Awesome Skills.",
"author": {
"name": "sickn33 and contributors",
@@ -1,6 +1,6 @@
{
"name": "agyb-aas-ai-product-evaluation-ops",
"version": "13.3.0",
"version": "13.4.0",
"description": "Install the \"AAS AI Product & Evaluation Ops\" workflow plugin from Antigravity Awesome Skills.",
"author": {
"name": "sickn33 and contributors",
@@ -1,6 +1,6 @@
{
"name": "antigravity-bundle-aas-api-platform-builder",
"version": "13.3.0",
"version": "13.4.0",
"description": "Editorial \"AAS API Platform Builder\" bundle for Claude Code from Antigravity Awesome Skills.",
"author": {
"name": "sickn33 and contributors",
@@ -1,6 +1,6 @@
{
"name": "agyb-aas-api-platform-builder",
"version": "13.3.0",
"version": "13.4.0",
"description": "Install the \"AAS API Platform Builder\" workflow plugin from Antigravity Awesome Skills.",
"author": {
"name": "sickn33 and contributors",
@@ -1,6 +1,6 @@
{
"name": "antigravity-bundle-aas-automation-builder",
"version": "13.3.0",
"version": "13.4.0",
"description": "Editorial \"AAS Automation Builder\" bundle for Claude Code from Antigravity Awesome Skills.",
"author": {
"name": "sickn33 and contributors",
@@ -1,6 +1,6 @@
{
"name": "agyb-aas-automation-builder",
"version": "13.3.0",
"version": "13.4.0",
"description": "Install the \"AAS Automation Builder\" workflow plugin from Antigravity Awesome Skills.",
"author": {
"name": "sickn33 and contributors",
@@ -1,6 +1,6 @@
{
"name": "antigravity-bundle-aas-data-analytics",
"version": "13.3.0",
"version": "13.4.0",
"description": "Editorial \"AAS Data Analytics\" bundle for Claude Code from Antigravity Awesome Skills.",
"author": {
"name": "sickn33 and contributors",
@@ -1,6 +1,6 @@
{
"name": "agyb-aas-data-analytics",
"version": "13.3.0",
"version": "13.4.0",
"description": "Install the \"AAS Data Analytics\" workflow plugin from Antigravity Awesome Skills.",
"author": {
"name": "sickn33 and contributors",
@@ -1,6 +1,6 @@
{
"name": "antigravity-bundle-aas-data-engineering-platform",
"version": "13.3.0",
"version": "13.4.0",
"description": "Editorial \"AAS Data Engineering Platform\" bundle for Claude Code from Antigravity Awesome Skills.",
"author": {
"name": "sickn33 and contributors",
@@ -1,6 +1,6 @@
{
"name": "agyb-aas-data-engineering-platform",
"version": "13.3.0",
"version": "13.4.0",
"description": "Install the \"AAS Data Engineering Platform\" workflow plugin from Antigravity Awesome Skills.",
"author": {
"name": "sickn33 and contributors",
@@ -1,6 +1,6 @@
{
"name": "antigravity-bundle-aas-devops-cloud",
"version": "13.3.0",
"version": "13.4.0",
"description": "Editorial \"AAS DevOps & Cloud\" bundle for Claude Code from Antigravity Awesome Skills.",
"author": {
"name": "sickn33 and contributors",
@@ -1,6 +1,6 @@
{
"name": "agyb-aas-devops-cloud",
"version": "13.3.0",
"version": "13.4.0",
"description": "Install the \"AAS DevOps & Cloud\" workflow plugin from Antigravity Awesome Skills.",
"author": {
"name": "sickn33 and contributors",
@@ -1,6 +1,6 @@
{
"name": "antigravity-bundle-aas-documents-presentations",
"version": "13.3.0",
"version": "13.4.0",
"description": "Editorial \"AAS Documents & Presentations\" bundle for Claude Code from Antigravity Awesome Skills.",
"author": {
"name": "sickn33 and contributors",
@@ -1,6 +1,6 @@
{
"name": "agyb-aas-documents-presentations",
"version": "13.3.0",
"version": "13.4.0",
"description": "Install the \"AAS Documents & Presentations\" workflow plugin from Antigravity Awesome Skills.",
"author": {
"name": "sickn33 and contributors",
@@ -1,6 +1,6 @@
{
"name": "antigravity-bundle-aas-localization-international-growth",
"version": "13.3.0",
"version": "13.4.0",
"description": "Editorial \"AAS Localization & International Growth\" bundle for Claude Code from Antigravity Awesome Skills.",
"author": {
"name": "sickn33 and contributors",
@@ -1,6 +1,6 @@
{
"name": "agyb-aas-localization-international-growth",
"version": "13.3.0",
"version": "13.4.0",
"description": "Install the \"AAS Localization & International Growth\" workflow plugin from Antigravity Awesome Skills.",
"author": {
"name": "sickn33 and contributors",
@@ -1,6 +1,6 @@
{
"name": "antigravity-bundle-aas-marketing-seo-growth",
"version": "13.3.0",
"version": "13.4.0",
"description": "Editorial \"AAS Marketing, SEO & Growth\" bundle for Claude Code from Antigravity Awesome Skills.",
"author": {
"name": "sickn33 and contributors",
@@ -1,6 +1,6 @@
{
"name": "agyb-aas-marketing-seo-growth",
"version": "13.3.0",
"version": "13.4.0",
"description": "Install the \"AAS Marketing, SEO & Growth\" workflow plugin from Antigravity Awesome Skills.",
"author": {
"name": "sickn33 and contributors",
@@ -1,6 +1,6 @@
{
"name": "antigravity-bundle-aas-mobile-app-builder",
"version": "13.3.0",
"version": "13.4.0",
"description": "Editorial \"AAS Mobile App Builder\" bundle for Claude Code from Antigravity Awesome Skills.",
"author": {
"name": "sickn33 and contributors",
@@ -1,6 +1,6 @@
{
"name": "agyb-aas-mobile-app-builder",
"version": "13.3.0",
"version": "13.4.0",
"description": "Install the \"AAS Mobile App Builder\" workflow plugin from Antigravity Awesome Skills.",
"author": {
"name": "sickn33 and contributors",
@@ -1,6 +1,6 @@
{
"name": "antigravity-bundle-aas-observability-ir",
"version": "13.3.0",
"version": "13.4.0",
"description": "Editorial \"AAS Observability IR\" bundle for Claude Code from Antigravity Awesome Skills.",
"author": {
"name": "sickn33 and contributors",
@@ -1,6 +1,6 @@
{
"name": "agyb-aas-observability-ir",
"version": "13.3.0",
"version": "13.4.0",
"description": "Install the \"AAS Observability IR\" workflow plugin from Antigravity Awesome Skills.",
"author": {
"name": "sickn33 and contributors",
@@ -1,6 +1,6 @@
{
"name": "antigravity-bundle-aas-oss-maintainer",
"version": "13.3.0",
"version": "13.4.0",
"description": "Editorial \"AAS OSS Maintainer\" bundle for Claude Code from Antigravity Awesome Skills.",
"author": {
"name": "sickn33 and contributors",
@@ -1,6 +1,6 @@
{
"name": "agyb-aas-oss-maintainer",
"version": "13.3.0",
"version": "13.4.0",
"description": "Install the \"AAS OSS Maintainer\" workflow plugin from Antigravity Awesome Skills.",
"author": {
"name": "sickn33 and contributors",
@@ -1,6 +1,6 @@
{
"name": "antigravity-bundle-aas-privacy-compliance-engineering",
"version": "13.3.0",
"version": "13.4.0",
"description": "Editorial \"AAS Privacy & Compliance Engineering\" bundle for Claude Code from Antigravity Awesome Skills.",
"author": {
"name": "sickn33 and contributors",
@@ -1,6 +1,6 @@
{
"name": "agyb-aas-privacy-compliance-engineering",
"version": "13.3.0",
"version": "13.4.0",
"description": "Install the \"AAS Privacy & Compliance Engineering\" workflow plugin from Antigravity Awesome Skills.",
"author": {
"name": "sickn33 and contributors",
@@ -1,6 +1,6 @@
{
"name": "antigravity-bundle-aas-product-design-studio",
"version": "13.3.0",
"version": "13.4.0",
"description": "Editorial \"AAS Product Design Studio\" bundle for Claude Code from Antigravity Awesome Skills.",
"author": {
"name": "sickn33 and contributors",
@@ -1,6 +1,6 @@
{
"name": "agyb-aas-product-design-studio",
"version": "13.3.0",
"version": "13.4.0",
"description": "Install the \"AAS Product Design Studio\" workflow plugin from Antigravity Awesome Skills.",
"author": {
"name": "sickn33 and contributors",
@@ -1,6 +1,6 @@
{
"name": "antigravity-bundle-aas-python-api-builder",
"version": "13.3.0",
"version": "13.4.0",
"description": "Editorial \"AAS Python API Builder\" bundle for Claude Code from Antigravity Awesome Skills.",
"author": {
"name": "sickn33 and contributors",
@@ -1,6 +1,6 @@
{
"name": "agyb-aas-python-api-builder",
"version": "13.3.0",
"version": "13.4.0",
"description": "Install the \"AAS Python API Builder\" workflow plugin from Antigravity Awesome Skills.",
"author": {
"name": "sickn33 and contributors",
@@ -1,6 +1,6 @@
{
"name": "antigravity-bundle-aas-qa-test-automation",
"version": "13.3.0",
"version": "13.4.0",
"description": "Editorial \"AAS QA & Test Automation\" bundle for Claude Code from Antigravity Awesome Skills.",
"author": {
"name": "sickn33 and contributors",
@@ -1,6 +1,6 @@
{
"name": "agyb-aas-qa-test-automation",
"version": "13.3.0",
"version": "13.4.0",
"description": "Install the \"AAS QA & Test Automation\" workflow plugin from Antigravity Awesome Skills.",
"author": {
"name": "sickn33 and contributors",
@@ -1,6 +1,6 @@
{
"name": "antigravity-bundle-aas-saas-launch-revenue",
"version": "13.3.0",
"version": "13.4.0",
"description": "Editorial \"AAS SaaS Launch & Revenue\" bundle for Claude Code from Antigravity Awesome Skills.",
"author": {
"name": "sickn33 and contributors",
@@ -1,6 +1,6 @@
{
"name": "agyb-aas-saas-launch-revenue",
"version": "13.3.0",
"version": "13.4.0",
"description": "Install the \"AAS SaaS Launch & Revenue\" workflow plugin from Antigravity Awesome Skills.",
"author": {
"name": "sickn33 and contributors",
@@ -1,6 +1,6 @@
{
"name": "antigravity-bundle-aas-secure-app-builder",
"version": "13.3.0",
"version": "13.4.0",
"description": "Editorial \"AAS Secure App Builder\" bundle for Claude Code from Antigravity Awesome Skills.",
"author": {
"name": "sickn33 and contributors",
@@ -1,6 +1,6 @@
{
"name": "agyb-aas-secure-app-builder",
"version": "13.3.0",
"version": "13.4.0",
"description": "Install the \"AAS Secure App Builder\" workflow plugin from Antigravity Awesome Skills.",
"author": {
"name": "sickn33 and contributors",
@@ -1,6 +1,6 @@
{
"name": "antigravity-bundle-aas-security-engineer",
"version": "13.3.0",
"version": "13.4.0",
"description": "Editorial \"AAS Security Engineer\" bundle for Claude Code from Antigravity Awesome Skills.",
"author": {
"name": "sickn33 and contributors",
@@ -1,6 +1,6 @@
{
"name": "agyb-aas-security-engineer",
"version": "13.3.0",
"version": "13.4.0",
"description": "Install the \"AAS Security Engineer\" workflow plugin from Antigravity Awesome Skills.",
"author": {
"name": "sickn33 and contributors",
@@ -1,6 +1,6 @@
{
"name": "antigravity-bundle-aas-web-app-builder",
"version": "13.3.0",
"version": "13.4.0",
"description": "Editorial \"AAS Web App Builder\" bundle for Claude Code from Antigravity Awesome Skills.",
"author": {
"name": "sickn33 and contributors",
@@ -1,6 +1,6 @@
{
"name": "agyb-aas-web-app-builder",
"version": "13.3.0",
"version": "13.4.0",
"description": "Install the \"AAS Web App Builder\" workflow plugin from Antigravity Awesome Skills.",
"author": {
"name": "sickn33 and contributors",
@@ -1,6 +1,6 @@
{
"name": "antigravity-bundle-agent-architect",
"version": "13.3.0",
"version": "13.4.0",
"description": "Editorial \"Agent Architect\" bundle for Claude Code from Antigravity Awesome Skills.",
"author": {
"name": "sickn33 and contributors",
@@ -1,6 +1,6 @@
{
"name": "agyb-agent-architect",
"version": "13.3.0",
"version": "13.4.0",
"description": "Install the \"Agent Architect\" editorial skill bundle from Antigravity Awesome Skills.",
"author": {
"name": "sickn33 and contributors",
@@ -1,6 +1,6 @@
{
"name": "antigravity-bundle-apple-platform-design",
"version": "13.3.0",
"version": "13.4.0",
"description": "Editorial \"Apple Platform Design\" bundle for Claude Code from Antigravity Awesome Skills.",
"author": {
"name": "sickn33 and contributors",
@@ -1,6 +1,6 @@
{
"name": "agyb-apple-platform-design",
"version": "13.3.0",
"version": "13.4.0",
"description": "Install the \"Apple Platform Design\" editorial skill bundle from Antigravity Awesome Skills.",
"author": {
"name": "sickn33 and contributors",
@@ -1,6 +1,6 @@
{
"name": "antigravity-bundle-architecture-design",
"version": "13.3.0",
"version": "13.4.0",
"description": "Editorial \"Architecture & Design\" bundle for Claude Code from Antigravity Awesome Skills.",
"author": {
"name": "sickn33 and contributors",
@@ -1,6 +1,6 @@
{
"name": "agyb-architecture-design",
"version": "13.3.0",
"version": "13.4.0",
"description": "Install the \"Architecture & Design\" editorial skill bundle from Antigravity Awesome Skills.",
"author": {
"name": "sickn33 and contributors",
@@ -1,6 +1,6 @@
{
"name": "antigravity-bundle-automation-builder",
"version": "13.3.0",
"version": "13.4.0",
"description": "Editorial \"Automation Builder\" bundle for Claude Code from Antigravity Awesome Skills.",
"author": {
"name": "sickn33 and contributors",
@@ -1,6 +1,6 @@
{
"name": "agyb-automation-builder",
"version": "13.3.0",
"version": "13.4.0",
"description": "Install the \"Automation Builder\" editorial skill bundle from Antigravity Awesome Skills.",
"author": {
"name": "sickn33 and contributors",
@@ -1,6 +1,6 @@
{
"name": "antigravity-bundle-azure-ai-cloud",
"version": "13.3.0",
"version": "13.4.0",
"description": "Editorial \"Azure AI & Cloud\" bundle for Claude Code from Antigravity Awesome Skills.",
"author": {
"name": "sickn33 and contributors",
@@ -1,6 +1,6 @@
{
"name": "agyb-azure-ai-cloud",
"version": "13.3.0",
"version": "13.4.0",
"description": "Install the \"Azure AI & Cloud\" editorial skill bundle from Antigravity Awesome Skills.",
"author": {
"name": "sickn33 and contributors",
@@ -1,6 +1,6 @@
{
"name": "antigravity-bundle-business-analyst",
"version": "13.3.0",
"version": "13.4.0",
"description": "Editorial \"Business Analyst\" bundle for Claude Code from Antigravity Awesome Skills.",
"author": {
"name": "sickn33 and contributors",
@@ -1,6 +1,6 @@
{
"name": "agyb-business-analyst",
"version": "13.3.0",
"version": "13.4.0",
"description": "Install the \"Business Analyst\" editorial skill bundle from Antigravity Awesome Skills.",
"author": {
"name": "sickn33 and contributors",
@@ -1,6 +1,6 @@
{
"name": "antigravity-bundle-commerce-payments",
"version": "13.3.0",
"version": "13.4.0",
"description": "Editorial \"Commerce & Payments\" bundle for Claude Code from Antigravity Awesome Skills.",
"author": {
"name": "sickn33 and contributors",
@@ -1,6 +1,6 @@
{
"name": "agyb-commerce-payments",
"version": "13.3.0",
"version": "13.4.0",
"description": "Install the \"Commerce & Payments\" editorial skill bundle from Antigravity Awesome Skills.",
"author": {
"name": "sickn33 and contributors",
@@ -1,6 +1,6 @@
{
"name": "antigravity-bundle-creative-director",
"version": "13.3.0",
"version": "13.4.0",
"description": "Editorial \"Creative Director\" bundle for Claude Code from Antigravity Awesome Skills.",
"author": {
"name": "sickn33 and contributors",
@@ -1,6 +1,6 @@
{
"name": "agyb-creative-director",
"version": "13.3.0",
"version": "13.4.0",
"description": "Install the \"Creative Director\" editorial skill bundle from Antigravity Awesome Skills.",
"author": {
"name": "sickn33 and contributors",
@@ -1,6 +1,6 @@
{
"name": "antigravity-bundle-data-analytics",
"version": "13.3.0",
"version": "13.4.0",
"description": "Editorial \"Data & Analytics\" bundle for Claude Code from Antigravity Awesome Skills.",
"author": {
"name": "sickn33 and contributors",
@@ -1,6 +1,6 @@
{
"name": "agyb-data-analytics",
"version": "13.3.0",
"version": "13.4.0",
"description": "Install the \"Data & Analytics\" editorial skill bundle from Antigravity Awesome Skills.",
"author": {
"name": "sickn33 and contributors",
@@ -1,6 +1,6 @@
{
"name": "antigravity-bundle-data-engineering",
"version": "13.3.0",
"version": "13.4.0",
"description": "Editorial \"Data Engineering\" bundle for Claude Code from Antigravity Awesome Skills.",
"author": {
"name": "sickn33 and contributors",
@@ -1,6 +1,6 @@
{
"name": "agyb-data-engineering",
"version": "13.3.0",
"version": "13.4.0",
"description": "Install the \"Data Engineering\" editorial skill bundle from Antigravity Awesome Skills.",
"author": {
"name": "sickn33 and contributors",
@@ -1,6 +1,6 @@
{
"name": "antigravity-bundle-ddd-evented-architecture",
"version": "13.3.0",
"version": "13.4.0",
"description": "Editorial \"DDD & Evented Architecture\" bundle for Claude Code from Antigravity Awesome Skills.",
"author": {
"name": "sickn33 and contributors",
@@ -1,6 +1,6 @@
{
"name": "agyb-ddd-evented-architecture",
"version": "13.3.0",
"version": "13.4.0",
"description": "Install the \"DDD & Evented Architecture\" editorial skill bundle from Antigravity Awesome Skills.",
"author": {
"name": "sickn33 and contributors",
@@ -1,6 +1,6 @@
{
"name": "antigravity-bundle-devops-cloud",
"version": "13.3.0",
"version": "13.4.0",
"description": "Editorial \"DevOps & Cloud\" bundle for Claude Code from Antigravity Awesome Skills.",
"author": {
"name": "sickn33 and contributors",
@@ -1,6 +1,6 @@
{
"name": "agyb-devops-cloud",
"version": "13.3.0",
"version": "13.4.0",
"description": "Install the \"DevOps & Cloud\" editorial skill bundle from Antigravity Awesome Skills.",
"author": {
"name": "sickn33 and contributors",
@@ -1,6 +1,6 @@
{
"name": "antigravity-bundle-documents-presentations",
"version": "13.3.0",
"version": "13.4.0",
"description": "Editorial \"Documents & Presentations\" bundle for Claude Code from Antigravity Awesome Skills.",
"author": {
"name": "sickn33 and contributors",
@@ -1,6 +1,6 @@
{
"name": "agyb-documents-presentations",
"version": "13.3.0",
"version": "13.4.0",
"description": "Install the \"Documents & Presentations\" editorial skill bundle from Antigravity Awesome Skills.",
"author": {
"name": "sickn33 and contributors",
@@ -1,6 +1,6 @@
{
"name": "antigravity-bundle-essentials",
"version": "13.3.0",
"version": "13.4.0",
"description": "Editorial \"Essentials\" bundle for Claude Code from Antigravity Awesome Skills.",
"author": {
"name": "sickn33 and contributors",
@@ -1,6 +1,6 @@
{
"name": "agyb-essentials",
"version": "13.3.0",
"version": "13.4.0",
"description": "Install the \"Essentials\" editorial skill bundle from Antigravity Awesome Skills.",
"author": {
"name": "sickn33 and contributors",
@@ -1,6 +1,6 @@
{
"name": "antigravity-bundle-expo-react-native",
"version": "13.3.0",
"version": "13.4.0",
"description": "Editorial \"Expo & React Native\" bundle for Claude Code from Antigravity Awesome Skills.",
"author": {
"name": "sickn33 and contributors",
@@ -1,6 +1,6 @@
{
"name": "agyb-expo-react-native",
"version": "13.3.0",
"version": "13.4.0",
"description": "Install the \"Expo & React Native\" editorial skill bundle from Antigravity Awesome Skills.",
"author": {
"name": "sickn33 and contributors",
@@ -1,6 +1,6 @@
{
"name": "antigravity-bundle-full-stack-developer",
"version": "13.3.0",
"version": "13.4.0",
"description": "Editorial \"Full-Stack Developer\" bundle for Claude Code from Antigravity Awesome Skills.",
"author": {
"name": "sickn33 and contributors",
@@ -1,6 +1,6 @@
{
"name": "agyb-full-stack-developer",
"version": "13.3.0",
"version": "13.4.0",
"description": "Install the \"Full-Stack Developer\" editorial skill bundle from Antigravity Awesome Skills.",
"author": {
"name": "sickn33 and contributors",
@@ -1,6 +1,6 @@
{
"name": "antigravity-bundle-indie-game-dev",
"version": "13.3.0",
"version": "13.4.0",
"description": "Editorial \"Indie Game Dev\" bundle for Claude Code from Antigravity Awesome Skills.",
"author": {
"name": "sickn33 and contributors",
@@ -1,6 +1,6 @@
{
"name": "agyb-indie-game-dev",
"version": "13.3.0",
"version": "13.4.0",
"description": "Install the \"Indie Game Dev\" editorial skill bundle from Antigravity Awesome Skills.",
"author": {
"name": "sickn33 and contributors",
@@ -1,6 +1,6 @@
{
"name": "antigravity-bundle-integration-apis",
"version": "13.3.0",
"version": "13.4.0",
"description": "Editorial \"Integration & APIs\" bundle for Claude Code from Antigravity Awesome Skills.",
"author": {
"name": "sickn33 and contributors",
@@ -1,6 +1,6 @@
{
"name": "agyb-integration-apis",
"version": "13.3.0",
"version": "13.4.0",
"description": "Install the \"Integration & APIs\" editorial skill bundle from Antigravity Awesome Skills.",
"author": {
"name": "sickn33 and contributors",
@@ -1,6 +1,6 @@
{
"name": "antigravity-bundle-llm-application-developer",
"version": "13.3.0",
"version": "13.4.0",
"description": "Editorial \"LLM Application Developer\" bundle for Claude Code from Antigravity Awesome Skills.",
"author": {
"name": "sickn33 and contributors",
@@ -1,6 +1,6 @@
{
"name": "agyb-llm-application-developer",
"version": "13.3.0",
"version": "13.4.0",
"description": "Install the \"LLM Application Developer\" editorial skill bundle from Antigravity Awesome Skills.",
"author": {
"name": "sickn33 and contributors",
@@ -1,6 +1,6 @@
{
"name": "antigravity-bundle-makepad-builder",
"version": "13.3.0",
"version": "13.4.0",
"description": "Editorial \"Makepad Builder\" bundle for Claude Code from Antigravity Awesome Skills.",
"author": {
"name": "sickn33 and contributors",
@@ -1,6 +1,6 @@
{
"name": "agyb-makepad-builder",
"version": "13.3.0",
"version": "13.4.0",
"description": "Install the \"Makepad Builder\" editorial skill bundle from Antigravity Awesome Skills.",
"author": {
"name": "sickn33 and contributors",
@@ -1,6 +1,6 @@
{
"name": "antigravity-bundle-marketing-growth",
"version": "13.3.0",
"version": "13.4.0",
"description": "Editorial \"Marketing & Growth\" bundle for Claude Code from Antigravity Awesome Skills.",
"author": {
"name": "sickn33 and contributors",
@@ -1,6 +1,6 @@
{
"name": "agyb-marketing-growth",
"version": "13.3.0",
"version": "13.4.0",
"description": "Install the \"Marketing & Growth\" editorial skill bundle from Antigravity Awesome Skills.",
"author": {
"name": "sickn33 and contributors",

Some files were not shown because too many files have changed in this diff Show More