feat: add YAML frontmatter to deploy skill

This commit is contained in:
2026-06-07 14:34:45 -05:00
parent 3e53f7da7c
commit b9e946d686
+598 -23
View File
@@ -1,28 +1,603 @@
# deploy
---
name: deploy
description: >
Activates approved rules from the Tracker in Gmail. Use when the user says "deploy the
rules", "turn on the rules", "make the rules live", "activate my Gmail rules", "set up the
filters", or "I'm ready to go live". Implements gmail_filter_safe rules via Google Workspace
MCP directly, walks the user through installing apps_script_needed rules via Google Apps
Script with a step-by-step walkthrough, and parks studio_candidate rules for future use.
Also creates the "! Rule Needed" label on first deploy. Runs after import approves rules.
Part of the Gmail Inbox Architect plugin.
---
## Trigger
Use this skill when approved rules are ready to be implemented in Gmail, the user says
"deploy the rules", "create the filters", "let's push this batch", or a specific rule
set has been approved through the `import` review process.
# deploy — Gmail Inbox Architect
## Purpose
Implements approved rules from the Tracker into Gmail. This means:
- Gmail filters via Google Workspace MCP (Claude executes directly)
- Apps Script deployment via step-by-step user walkthrough (Claude guides, user executes)
## What This Skill Does
## Non-Technical User Standard
This skill must never assume the user knows what a Gmail filter is, what Apps Script is,
or how to navigate Google's admin interfaces. Every instruction must be written in plain
English with numbered steps, screenshots described in words, and explicit "click X, then Y"
guidance.
Takes approved rules from the Tracker and makes them live. There are three deployment
paths depending on the rule type:
## Bulk Operation Rule (North Star)
For operations involving hundreds or thousands of existing emails (mass label changes,
backfills, restructuring), Claude must:
1. Recommend the lowest-cost path first (Gmail UI, a pre-built script, or a task order
to a lower-cost agent)
2. Guide the user through that path OR produce a task order that handles it
3. Never burn Claude tokens on mechanical, row-by-row email operations
- **Gmail sorting rules** (`gmail_filter_safe`) — Claude creates these directly via its
Gmail connection. Fast, no user action required beyond confirming.
- **Automation scripts** (`apps_script_needed`) — Claude writes a script, walks the user
through installing it in Google Apps Script, guides the test run, and reviews the logs.
- **Studio candidates** (`studio_candidate`) — acknowledged and parked. No deployment
until Studio support is added to this plugin.
## Skill Stub — Implementation Pending
Full SKILL.md to be written in build session. Load CW-020_Concept.md for scope and design intent.
Nothing goes live without explicit user confirmation. All deployed rules are marked active
in the Tracker after successful deployment.
This skill is entirely self-contained. All templates, script patterns, and walkthrough
steps are embedded below.
---
## When This Skill Is Triggered
- User says: "deploy the rules", "turn on the rules", "set up the filters",
"let's push this batch", "go live", "activate the rules"
- Import skill directs the user here after the review queue is complete
- User says: "the script is ready to deploy", "install the automation"
---
## Prerequisites
Before deploying, verify:
- `_status.md` exists with Phase = "Rule Build" or "Deploy-ready"
- Tracker exists with at least one rule where `enabled = FALSE` and `deployability` is set
- Bible.md exists (needed to confirm label taxonomy before creating filters)
Read the `apps_script_log.md` file from the project folder if it exists. This file tracks
previous script deployments and known issues for this project. Reference it before writing
any new Apps Script code to avoid repeating past mistakes.
---
## Dependencies
- **Google Workspace MCP** — for Gmail filter creation, label ID lookup, and Gmail search
during post-deploy verification
- **No other external dependencies** — Apps Script code is generated inline
---
## Execution Flow
```
Phase 1 → Sanity check and queue load (what's ready to deploy)
Phase 2 → Deploy gmail_filter_safe rules (Claude executes via Gmail connection)
Phase 3 → Generate and deploy apps_script_needed rules (walkthrough)
Phase 4 → Acknowledge studio_candidate rules (park, no action)
Phase 5 → Update Tracker (mark deployed rules as enabled=TRUE)
Phase 6 → Update _status.md and close out
```
Phases 2, 3, and 4 are independent — run whichever apply based on what's in the queue.
It is valid to do Phase 2 only (no Apps Script rules in this batch), or Phase 3 only.
---
## Phase 1 — Sanity Check and Queue Load
Read `_status.md` and the Tracker.
From the Tracker, load all rules where `enabled = FALSE`. Group by deployability:
- `gmail_filter_safe` → Phase 2 queue
- `apps_script_needed` → Phase 3 queue
- `studio_candidate` → Phase 4 queue
Also load any rules where `enabled = FALSE` and `deployability` is blank — treat these
as needing a deployability decision. Ask Claude which path they belong on before proceeding.
Present the deployment plan:
> "Here's what's ready to go live:
>
> [N] sorting rules — I can create these directly in Gmail right now.
> [N] automation script rules — these need a small script installed in Google Apps Script.
> [N] parked rules — saved for a future step.
>
> Want to start with the sorting rules? That's the fastest part."
---
## Phase 2 — Deploy Gmail Filter Rules
### Step 2.1 — Present for final confirmation
Before creating any filters, show a clean summary. Group rules by target label.
> "Here are the [N] sorting rules I'm about to create. Take a look before I turn them on:
>
> **Finance/AMEX** — email from americanexpress.com with 'statement' in the subject
> **Vendors/Shipping** — email from ups.com, fedex.com — automatically filed, marked as read
> **Clients/Transit** — any email where [client domain] is sender or recipient
> [... etc]
>
> Once I create these, new email will start sorting automatically. Ready?"
Wait for explicit confirmation. Do not proceed without it.
### Step 2.2 — Look up label IDs
Call `list_gmail_labels` to get the Gmail label ID for each target label.
Gmail filters require label IDs, not label names.
Map each `base_label` value in the queue to its corresponding label ID.
If a label is missing (not found in Gmail):
> "I can't find the label '[label name]' in Gmail — it may not have been created yet.
> Do you want me to create it now, or skip this rule for now?"
Wait for answer before proceeding.
### Step 2.3 — Create filters
For each gmail_filter_safe rule, call `manage_gmail_filter` with:
**Criteria** (build from Tracker fields):
- `from_domain`: build as `from:domain.com` — multiple domains use OR logic:
`{from:domain1.com from:domain2.com}`
- `to_or_cc_domain` (participant pattern): combine as:
`{from:domain.com to:domain.com}`
- `subject_contains`: `subject:keyword` — multiple keywords joined with OR or AND
based on the rule's intent (default OR unless notes specify otherwise)
- `has_attachment`: `has:attachment`
- Combined signals: join with spaces (AND logic in Gmail search)
**Actions** (from Tracker fields):
- `addLabelIds`: the label ID from Step 2.2
- `markAsRead`: TRUE if `mark_read = TRUE`
- Skip inbox (archive): set `removeLabelIds: ["INBOX"]` if `archive = TRUE`
Create one filter per rule. Do not batch multiple rules into one filter — they are
harder to manage and delete individually.
After each successful creation, note it. If a creation fails, log the error and continue
with the remaining rules — do not abort the entire batch.
### Step 2.4 — Post-deploy verification
After all filters are created, run a quick spot-check. For 2-3 of the higher-volume rules,
call `search_gmail_messages` using the same query and confirm Gmail returns the expected
threads. This validates the filter syntax fired correctly.
Report back:
> "Done — [N] sorting rules are now active in Gmail. New email matching these patterns
> will be sorted automatically from this point forward.
>
> [If any failed:] [N] rules couldn't be created — [plain English reason]. Want to
> troubleshoot those now or skip them?"
---
## Phase 3 — Apps Script Deployment
Apps Script handles rules that Gmail's native filters can't: attachment filename patterns,
body text signals, multi-condition logic.
### Step 3.1 — Read the issue log
Before writing any code, check for `apps_script_log.md` in the project Drive folder.
If it exists, read it. Note any previous errors or patterns to avoid.
### Step 3.2 — Generate the script
Write a complete, runnable Google Apps Script file from the apps_script_needed rules
in the Tracker. The script must be syntactically correct, well-commented, and ready
to paste without modification.
**Script architecture:**
```javascript
/**
* Gmail Classification Script
* Project: Gmail Inbox Architect — [account]
* Generated: [date]
* Rules covered: [comma-separated list of rule_ids]
*
* BEFORE RUNNING:
* 1. Set DRY_RUN = true for your first test run
* 2. Review the execution log after the test
* 3. Set DRY_RUN = false only after confirming the test looks correct
*
* Issue log: apps_script_log.md in your project Drive folder
*/
// ─── CONFIGURATION ────────────────────────────────────────────────────────────
const DRY_RUN = true; // true = log only, no changes. Set false after testing.
const MAX_THREADS = 50; // Threads to process per rule per run (raise if needed)
const LOOKBACK_HOURS = 2; // Only check email newer than this many hours
// ──────────────────────────────────────────────────────────────────────────────
function runClassification() {
const startTime = new Date();
Logger.log('=== Gmail Classification Run ===');
Logger.log('Mode: ' + (DRY_RUN ? 'DRY RUN (no changes)' : 'LIVE'));
Logger.log('Started: ' + startTime.toISOString());
const rules = buildRules();
let totalLabeled = 0;
const errors = [];
for (const rule of rules) {
try {
const count = applyRule(rule);
totalLabeled += count;
if (count > 0) {
Logger.log('[' + rule.id + '] Applied to ' + count + ' thread(s)');
}
} catch (e) {
const msg = '[' + rule.id + '] ERROR: ' + e.message;
Logger.log(msg);
errors.push(msg);
}
}
Logger.log('---');
Logger.log('Total threads processed: ' + totalLabeled);
Logger.log('Errors: ' + errors.length);
Logger.log('Finished: ' + new Date().toISOString());
if (errors.length > 0) {
Logger.log('ERROR DETAILS:');
errors.forEach(e => Logger.log(e));
}
}
function applyRule(rule) {
// Build time-bounded query so we only process recent email
const cutoff = new Date(Date.now() - LOOKBACK_HOURS * 60 * 60 * 1000);
const after = Utilities.formatDate(cutoff, 'UTC', 'yyyy/MM/dd');
const query = rule.query + ' after:' + after;
const threads = GmailApp.search(query, 0, MAX_THREADS);
if (threads.length === 0) return 0;
const label = rule.labelName
? GmailApp.getUserLabelByName(rule.labelName)
: null;
if (rule.labelName && !label) {
throw new Error('Label not found: ' + rule.labelName +
' — create this label in Gmail first');
}
let count = 0;
for (const thread of threads) {
const subject = thread.getFirstMessageSubject();
if (DRY_RUN) {
Logger.log(' [DRY RUN] Would label "' + subject + '" → ' + rule.labelName);
} else {
if (label) label.addToThread(thread);
if (rule.archive) thread.moveToArchive();
if (rule.markRead) thread.markRead();
Logger.log(' Labeled: "' + subject + '" → ' + rule.labelName);
}
count++;
}
return count;
}
function buildRules() {
// Each entry below is one rule from the Tracker.
// query: Gmail search query that identifies matching threads
// labelName: exact Gmail label path (must exist in Gmail already)
// archive: move out of inbox after labeling
// markRead: mark as read after labeling (use sparingly)
return [
[RULES_ARRAY generated per rule below]
];
}
```
**Query building per rule type:**
For each `apps_script_needed` rule in the Tracker, build a `query` string:
- `from_domain` only: `"from:domain.com"`
- `from_domain` + `subject_contains`: `"from:domain.com subject:keyword"`
- `has_attachment` = TRUE: append `"has:attachment"`
- `body_signal` (body text): use bare keyword (Gmail search searches body by default):
`"from:domain.com invoice"` — note this in the rule's comment
- Attachment filename pattern: Gmail search does not support filename regex natively;
use `"has:attachment from:domain.com"` as the filter and note in comments that
the Apps Script `getAttachments()` check refines this. Add attachment name check
inline using `thread.getMessages()[0].getAttachments()` pattern match if the rule
requires filename specificity.
**Attachment filename check pattern (embed when needed):**
```javascript
// Check attachment filename pattern
const messages = thread.getMessages();
let hasMatchingAttachment = false;
for (const msg of messages) {
for (const att of msg.getAttachments()) {
if (/\.(stp|step)$/i.test(att.getName())) {
hasMatchingAttachment = true;
break;
}
}
if (hasMatchingAttachment) break;
}
if (!hasMatchingAttachment) continue; // skip thread
```
**After writing the script:**
Show it to Claude for review before presenting to the user:
> "Here's the script I've written. It covers [N] rules. Take a look — I'll walk you
> through installing it once you're ready."
Then present the script to the user in a code block they can copy.
### Step 3.3 — Installation walkthrough
Walk the user through step by step. Use numbered steps, plain English.
> "Here's how to install this. It sounds more technical than it is — just follow
> the steps and let me know if anything looks different from what I describe.
>
> **Step 1 — Open Google Apps Script**
> Go to script.google.com. You may need to sign in with your Google account.
>
> **Step 2 — Create a new project**
> Click the blue '+ New project' button in the top left.
> You'll see a page with some starter code already in it.
>
> **Step 3 — Replace the existing code**
> Click anywhere in the code editor. Press Ctrl+A (Windows) or Cmd+A (Mac) to
> select everything. Then delete it. Paste the script I gave you above.
>
> **Step 4 — Name the project**
> Click 'Untitled project' at the top left and rename it to:
> 'Gmail Classifier — [account]'
> Then press Enter.
>
> **Step 5 — Save**
> Press Ctrl+S (Windows) or Cmd+S (Mac). You should see the save icon stop spinning.
>
> Let me know when you've done these steps and I'll walk you through the test run."
Wait for confirmation before continuing.
### Step 3.4 — Test run walkthrough
> "Now let's test it. The script is set to 'dry run' mode right now — it will tell
> you what it *would* do without actually changing anything.
>
> **Step 6 — Run the test**
> At the top of the script editor, you'll see a dropdown that says 'runClassification'.
> Make sure that's selected, then click the triangle (▶) Run button.
>
> **Step 7 — Authorize the script**
> A popup will appear asking for permission. Click 'Review permissions', choose your
> Google account, then click 'Allow'. This lets the script read your Gmail.
> (This only happens the first time.)
>
> **Step 8 — View the logs**
> After the run finishes, click 'Execution log' at the bottom of the screen.
> You'll see a list of what the script found.
>
> Copy everything from the execution log and paste it back to me."
Wait for the user to paste the logs.
### Step 3.5 — Review the test logs
Read the pasted execution log. Check for:
- **Errors**: any `[ERROR]` lines → diagnose and fix the script
- **Unexpected matches**: threads labeled that shouldn't be → tighten the query
- **Missing matches**: expected threads not found → check the query or LOOKBACK_HOURS
- **Label not found errors**: the Gmail label doesn't exist yet → create it first
If the log looks clean:
> "The test looks good — the script found [N] threads that match the rules, and nothing
> looks out of place. Ready to go live?
>
> Here's what you need to change: open the script, find the line that says
> `const DRY_RUN = true;` near the top, and change `true` to `false`. Save it.
> That's the only change."
If there are issues, fix the script, show the corrected version, and repeat from Step 3.4.
### Step 3.6 — Set up the trigger
After the user confirms DRY_RUN = false:
> "Last step — let's set it to run automatically.
>
> **Step 9 — Open triggers**
> In the script editor, click the clock icon on the left sidebar (it looks like a
> stopwatch). Or go to Extensions → Apps Script (if you're in Google Sheets) → Triggers.
>
> Actually, since you're already in the editor: look for 'Triggers' in the left sidebar.
> It's the icon that looks like an alarm clock. Click it.
>
> **Step 10 — Add a trigger**
> Click '+ Add Trigger' in the bottom right.
>
> Set these options:
> - Function to run: runClassification
> - Deployment: Head
> - Event source: Time-driven
> - Type of time-based trigger: Hour timer
> - Every: [1 hour is a good starting point — suggest 15 minutes if the user
> wants near-real-time; suggest 6 hours if they prefer low noise]
>
> Click Save.
>
> The script will now run automatically on that schedule."
### Step 3.7 — Escalation path
At any point in the walkthrough where the user is uncertain or stuck:
> "If any of this feels unclear or you want someone to help install it, this is a
> great time to loop in one of your team's AI experts or technical contacts.
> The script and all these instructions are saved in your project Drive folder —
> they'll have everything they need to get it set up."
Log this suggestion in `apps_script_log.md` as a note if the user escalates.
---
## Phase 4 — Studio Candidates
After handling the two active deployment paths, acknowledge parked rules:
> "[N] rules are saved for a future step — these are cases where the pattern is
> too fuzzy for the current tools to handle reliably. They're in your Tracker and
> won't be lost. When we add Studio support to this system, we'll come back to them."
No action required. These remain in the Tracker with `enabled = FALSE` and
`deployability = studio_candidate`.
---
## Phase 5 — Update Tracker
After successful deployment, update each deployed rule in the Tracker:
- Set `enabled = TRUE`
- Append to `notes`: "Deployed [date] via [gmail_filter / apps_script v[N]]"
For rules that failed deployment:
- Leave `enabled = FALSE`
- Append to `notes`: "Deploy attempted [date] — failed: [reason]"
For Apps Script rules deployed via script: update all rules covered by the script
to `enabled = TRUE` together, since they go live as a bundle when DRY_RUN is set false.
---
## Phase 6 — Update apps_script_log.md
After any Apps Script deployment (successful or not), update or create
`apps_script_log.md` in the project Drive folder.
**File format:**
```markdown
# Apps Script Log
## Project: [Gmail account]
## Purpose: Read this file before writing any new Apps Script for this project.
## Reference it to avoid repeating past mistakes.
---
## Script v[N] — [date]
**Rules covered:** [comma-separated rule_ids]
**Status:** [Testing / Active / Retired]
**DRY_RUN disabled:** [date]
**Trigger:** Every [N] hours
### Known issues
[List any errors encountered during testing or after going live]
### Resolutions
[What was changed to fix each issue]
### Notes
[Anything the next session should know before touching this script]
```
If the file already exists, append a new version block. Do not overwrite old entries —
the history is the point.
---
## Phase 7 — Update _status.md and Close Out
Update `_status.md`:
```
Last Updated: [timestamp]
Last Agent: Claude (CoWork) — deploy skill
Phase: [Deploy / Maintenance — depending on whether all rules are now live]
Last Completed Step: [N] gmail_filter_safe rules created. [N] apps_script_needed rules
deployed via Apps Script v[N]. [N] studio_candidate rules parked.
Pending Work: [NONE / or: [N] rules failed deployment — see notes]
Handoff Target: NONE
Notes: [Apps Script log updated. Tracker updated. Any escalations noted.]
```
Close out with the user:
**If everything deployed successfully:**
> "You're live. Here's what's now running:
>
> ✅ [N] sorting rules active in Gmail — new email will be sorted automatically
> ✅ [N] automation script rules running every [N] hours — checking for matching patterns
> 🅿️ [N] rules parked for future Studio support
>
> If you notice anything sorting incorrectly, say 'review a rule' and I'll help
> adjust it. If email stops sorting the way you expect, say 'check the script' and
> I'll look at the logs."
**If some rules failed:**
> "Most rules are live. [N] couldn't be deployed right now — [plain English summary].
> Want to troubleshoot those now or come back to them later?"
---
## Handling Bulk Backfill Requests
If the user asks to apply labels to thousands of existing emails (not just new ones
going forward), this is a separate operation from filter deployment. Apply the North
Star rule:
1. **Lowest-cost path first.** If the volume is manageable (under 500 threads per label),
Claude can run the Apps Script with a larger `MAX_THREADS` value and a wider lookback
window. Explain this option.
2. **For large volumes** (thousands of threads), produce a task order to a lower-cost
agent. Do not burn Claude tokens on mechanical row-by-row operations.
3. **Never auto-backfill.** Always confirm with the user before bulk-applying labels
to existing email.
---
## Non-Technical User Language Rules
Never use: filter, API, MCP, schema, regex, Apps Script (except when naming the product
explicitly), boolean, deploy, trigger (use "schedule" instead)
Substitute:
- "Gmail filter" → "sorting rule"
- "Apps Script" → "automation script" (or "Google's automation tool" on first mention)
- "trigger" → "schedule" or "automatic schedule"
- "deploy" → "turn on" or "go live"
- "boolean" → "yes/no"
- "query" → "search pattern"
Number every step in the walkthrough. Confirm before proceeding to the next major step.
---
## Safety Rules (Hardcoded — Cannot Be Overridden)
1. **Explicit confirmation before any Gmail mutation.** Present the full list of rules
to be deployed and wait for a clear "yes" before creating any filter or running
any live script.
2. **DRY_RUN = true in every generated script.** Never generate a script with
DRY_RUN = false. The user sets this manually after reviewing test logs.
3. **No filter creation without a valid label ID.** If a label doesn't exist in Gmail,
stop and ask. Do not create filters pointing to non-existent labels.
4. **No bulk backfill without explicit request and confirmation.** Filters and scripts
apply to new email only by default. Backfilling existing email requires a separate
explicit decision.
5. **Tracker updated after deployment.** Every deployed rule must be marked enabled=TRUE.
Every failed rule must have its failure noted. No silent state drift.
6. **apps_script_log.md always updated.** Never complete an Apps Script deployment
without updating the log file. The log is how future sessions avoid repeating mistakes.