From 88d7dfd853d8d96ca93682931196c4150863cc00 Mon Sep 17 00:00:00 2001 From: Michael Farrell Date: Thu, 9 Apr 2026 16:55:08 -0700 Subject: [PATCH 01/12] feat: add background-task skill and worktree shell helpers (#77) * feat: add background-task skill and worktree shell helpers Add a background-task skill for parallel worktree workflows, along with safe shell helper functions (worktree-git, worktree-rm, gempty-branches) that are auto-loaded via the existing preToolUse hook. Update the commit-and-push rule to document worktree commands and add .worktrees to .gitignore. * chore: remove gempty-branches helper Not needed for this repo. Remove the shell function and all references from the skill and commit-and-push rule. * style: format SKILL.md --- .cursor/hooks/shell-functions/worktree-git.sh | 19 ++ .cursor/hooks/shell-functions/worktree-rm.sh | 119 ++++++++ .cursor/rules/commit-and-push.mdc | 18 ++ .cursor/skills/background-task/SKILL.md | 257 ++++++++++++++++++ .gitignore | 1 + 5 files changed, 414 insertions(+) create mode 100644 .cursor/hooks/shell-functions/worktree-git.sh create mode 100644 .cursor/hooks/shell-functions/worktree-rm.sh create mode 100644 .cursor/skills/background-task/SKILL.md diff --git a/.cursor/hooks/shell-functions/worktree-git.sh b/.cursor/hooks/shell-functions/worktree-git.sh new file mode 100644 index 00000000..e9cc403b --- /dev/null +++ b/.cursor/hooks/shell-functions/worktree-git.sh @@ -0,0 +1,19 @@ +# Safe git command that only runs in .worktrees/ directories +# Prevents accidental git operations in main working directory +# +# Usage: +# cd .worktrees/fix-branch && worktree-git checkout -B branch origin/branch +# cd .worktrees/fix-branch && worktree-git add -A && worktree-git commit -m "fix: message" +# cd .worktrees/fix-branch && worktree-git push origin branch-name +worktree-git() { + if [[ "${PWD}" != *"/.worktrees/"* && "${PWD}" != *"/.worktrees" ]]; then + echo "❌ ERROR: worktree-git can only be run in .worktrees/ directories" + echo " Current directory: ${PWD}" + echo " Expected: */.worktrees/*" + echo "" + echo " This command is blocked to protect your working directory." + echo " Create a worktree first: git worktree add .worktrees/fix-{name} origin/{branch}" + return 1 + fi + git "$@" +} diff --git a/.cursor/hooks/shell-functions/worktree-rm.sh b/.cursor/hooks/shell-functions/worktree-rm.sh new file mode 100644 index 00000000..7dc105b2 --- /dev/null +++ b/.cursor/hooks/shell-functions/worktree-rm.sh @@ -0,0 +1,119 @@ +# Safely remove worktrees or files/directories within worktrees +# Only operates on paths containing /.worktrees/ to protect main workspace +# +# Usage: +# worktree-rm fix-branch # Remove .worktrees/fix-branch worktree +# worktree-rm .worktrees/fix-branch # Remove specific worktree +# worktree-rm # Remove current worktree (if in .worktrees/) +# worktree-rm node_modules # Remove node_modules in current worktree +# worktree-rm fix-branch/node_modules # Remove path in specific worktree +worktree-rm() { + local repo_dir + repo_dir="$(git rev-parse --show-toplevel 2>/dev/null || echo "")" + + # In a worktree, --show-toplevel returns the worktree root; walk up to find the real repo + if [[ -n "${repo_dir}" && "${repo_dir}" == *"/.worktrees/"* ]]; then + repo_dir="${repo_dir%%/.worktrees/*}" + fi + + if [[ -z "${repo_dir}" || ! -d "${repo_dir}/.git" ]]; then + if [[ -d "${HOME}/transcend/tools" ]]; then + repo_dir="${HOME}/transcend/tools" + else + echo "❌ ERROR: Could not determine repo directory" + echo " Run from within the tools repo or a worktree" + return 1 + fi + fi + + local worktrees_dir="${repo_dir}/.worktrees" + local target="${1:-${PWD}}" + + # If no argument and we're in a worktree, use the worktree root + if [[ -z "$1" && "${PWD}" == *"/.worktrees/"* ]]; then + local after_worktrees="${PWD#*/.worktrees/}" + local wt_name="${after_worktrees%%/*}" + target="${worktrees_dir}/${wt_name}" + fi + + # If the path is just a name (no slashes, doesn't start with . or /), prepend .worktrees/ + if [[ "${target}" != /* && "${target}" != .* && "${target}" != */* ]]; then + target="${worktrees_dir}/${target}" + fi + + # Handle relative paths starting with .worktrees/ + if [[ "${target}" == .worktrees/* ]]; then + target="${repo_dir}/${target}" + fi + + # If we're in a worktree and path is relative, resolve from current directory + if [[ "${PWD}" == *"/.worktrees/"* && "${target}" != /* ]]; then + target="${PWD}/${target}" + fi + + # If path looks like worktree-name/subpath, check if first component is a worktree + if [[ "${target}" != /* && "${target}" == */* ]]; then + local first_component="${target%%/*}" + if [[ -d "${worktrees_dir}/${first_component}" ]]; then + target="${worktrees_dir}/${target}" + fi + fi + + # Resolve to absolute path + if [[ "${target}" != /* ]]; then + target="$(cd "${repo_dir}" && realpath -m "${target}" 2>/dev/null || echo "${repo_dir}/${target}")" + fi + + # Safety check: ONLY operate on paths containing /.worktrees/ + if [[ "${target}" != *"/.worktrees/"* && "${target}" != *"/.worktrees" ]]; then + echo "❌ ERROR: worktree-rm can only operate on paths within .worktrees/" + echo " Target: ${target}" + echo " Expected: */.worktrees/*" + echo "" + echo " This command is blocked to protect your working directory." + return 1 + fi + + if [[ ! -e "${target}" ]]; then + echo "⚠️ Path does not exist: ${target}" + return 0 + fi + + # Determine if this is a worktree root or a path within a worktree + local worktree_name="${target#*/.worktrees/}" + worktree_name="${worktree_name%%/*}" + local worktree_path="${worktrees_dir}/${worktree_name}" + local is_worktree_root=false + + if [[ "${target}" == "${worktree_path}" ]]; then + is_worktree_root=true + fi + + # If we're currently in the directory we're trying to remove, cd out first + if [[ "${PWD}" == "${target}"* ]]; then + echo "📂 Moving out of target directory..." + cd "${repo_dir}" 2>/dev/null || cd ~ || return 1 + fi + + if [[ "${is_worktree_root}" == true ]]; then + echo "🧹 Removing worktree: ${target}" + if git -C "${repo_dir}" worktree remove "${target}" --force 2>/dev/null; then + echo "✅ Worktree removed successfully" + else + echo "📁 Not a registered git worktree, removing directory..." + rm -rf "${target}" + echo "✅ Directory removed" + fi + git -C "${repo_dir}" worktree prune 2>/dev/null + else + echo "🧹 Removing: ${target}" + if rm -rf "${target}"; then + echo "✅ Removed successfully" + else + echo "❌ Failed to remove: ${target}" + return 1 + fi + fi + + echo "🎉 Cleanup complete" +} diff --git a/.cursor/rules/commit-and-push.mdc b/.cursor/rules/commit-and-push.mdc index 92e51f47..417e53b5 100644 --- a/.cursor/rules/commit-and-push.mdc +++ b/.cursor/rules/commit-and-push.mdc @@ -6,6 +6,8 @@ alwaysApply: true # Safe Commands +## Main Workspace + ALWAYS use `tools-git` instead of `git` for ALL git commands (status, add, commit, push, log, diff, etc.). ALWAYS use `tools-rm` instead of `rm` for removing files or directories. NEVER use raw `git` or `rm` directly — they are not auto-allowed and the safe wrappers enforce repo boundaries. @@ -20,6 +22,22 @@ tools-git push origin branch-name tools-rm dist node_modules ``` +## Inside `.worktrees/` Directories + +Use `worktree-git` and `worktree-rm` instead — `tools-git` will NOT work in worktrees (repo name check fails). + +- `worktree-git` — safe git that only runs inside `.worktrees/` directories +- `worktree-rm` — safe rm that only operates on `.worktrees/` paths + +Examples: +``` +cd .worktrees/fix-branch && worktree-git add -A && worktree-git commit -m "fix: message" +cd .worktrees/fix-branch && worktree-git push origin HEAD:branch-name +worktree-rm fix-branch +``` + +See `/background-task` skill for the full worktree workflow. + # Committing and Pushing Changes ## Pre-commit hooks diff --git a/.cursor/skills/background-task/SKILL.md b/.cursor/skills/background-task/SKILL.md new file mode 100644 index 00000000..5db81fd8 --- /dev/null +++ b/.cursor/skills/background-task/SKILL.md @@ -0,0 +1,257 @@ +--- +name: background-task +description: Workflow for making changes to git branches WITHOUT affecting the user's current working directory. Use when asked to do anything "in the background" or when modifying a branch you're not currently on. +--- + +# Background Task + +Created By: Michael Farrell +Last Edited: April 9, 2026 + +## Quick Reference + +| Command | Use For | +| -------------------------------------------------------- | ----------------------------------------- | +| `git worktree add .worktrees/fix-{name} origin/{branch}` | Create worktree (safe in main workspace) | +| `worktree-git {any git command}` | ALL git operations in worktrees | +| `worktree-rm {name}` | Remove worktree or files within worktrees | +| `pnpm install --frozen-lockfile` | Install deps (no lockfile changes) | +| `pnpm install` | Install deps (lockfile update needed) | + +## Critical Rules (NEVER Violate) + +| Rule | Why | +| ------------------------------------------ | --------------------------- | +| **Never `git checkout` in main workspace** | Breaks user's workflow | +| **Always `worktree-git` in `.worktrees/`** | Auto-approved, safe | +| **Always `worktree-rm` for cleanup** | Auto-approved, safe | +| **Never `rm` in worktrees** | Requires approval, unsafe | +| **Never `tools-git` in worktrees** | Will fail (repo name check) | + +## When to Use + +- User says "in background", "fix branch X", "update branch without switching" +- You're about to run `git checkout` in the main workspace -- **STOP, use worktree instead** +- Any follow-up changes to a PR after worktree cleanup -- **Create NEW worktree** + +## Workflow + +### Step 1: Create Worktree + +```bash +cd "$(git rev-parse --show-toplevel)" && \ + git fetch origin {branch} main && \ + mkdir -p .worktrees && \ + worktree-rm fix-{name} 2>/dev/null || true && \ + git worktree prune && \ + git worktree add .worktrees/fix-{name} origin/{branch} +``` + +### Step 2: Install Dependencies + +```bash +cd .worktrees/fix-{name} && \ + pnpm install --frozen-lockfile +``` + +#### When Lockfile Updates Are Needed + +If you're adding a **new package** or modifying `package.json` dependencies, the lockfile must be updated. You'll see this error with `--frozen-lockfile`: + +``` +ERR_PNPM_FROZEN_LOCKFILE Cannot install with "frozen-lockfile" because pnpm-lock.yaml is not up to date +``` + +**Fix:** Drop the `--frozen-lockfile` flag: + +```bash +pnpm install +``` + +| Scenario | Command | +| ------------------------------------------- | -------------------------------- | +| Normal install (no new packages) | `pnpm install --frozen-lockfile` | +| Adding new package / lockfile update needed | `pnpm install` | + +### Step 3: Merge Main (REQUIRED) + +```bash +cd .worktrees/fix-{name} && \ + worktree-git fetch origin main && \ + BEHIND=$(worktree-git rev-list --count HEAD..origin/main) && \ + if [ "$BEHIND" -gt 0 ]; then \ + echo "Branch is $BEHIND commits behind main. Merging..." && \ + worktree-git merge origin/main -m "Merge branch 'main'" --no-edit; \ + fi +``` + +### Step 4: Make Changes + +```bash +cd .worktrees/fix-{name} && \ + worktree-git checkout -B {branch} origin/{branch} + # ... edit files ... +``` + +### Step 5: Build (if cross-package changes) + +When changes span multiple packages, build in dependency order: + +```bash +cd .worktrees/fix-{name} && \ + pnpm run --dir packages/utils build && \ + pnpm run --dir packages/sdk build && \ + pnpm run --dir packages/cli build +``` + +Skip packages that aren't affected by your changes. + +### Step 6: Commit and Push + +**Important:** When a worktree is first created with `git worktree add`, it is checked out in a detached-HEAD state. Always use `HEAD:{branch}` refspec when pushing to avoid silently pushing stale commits from the main workspace. Step 4's `checkout -B` attaches HEAD to the branch, but the explicit `HEAD:{branch}` form is always safe. + +```bash +cd .worktrees/fix-{name} && \ + pnpm run format && \ + worktree-git add -A && \ + worktree-git commit -m "fix: description" && \ + worktree-git push origin HEAD:{branch} +``` + +### Step 7: Create or Update PR + +Use `gh pr create --draft` for AI-generated PRs. + +### Step 8: Cleanup + +```bash +worktree-rm fix-{name} +``` + +## Batched Workflow (Single Command) + +```bash +# Setup + install + merge (one approval) +cd "$(git rev-parse --show-toplevel)" && \ + git fetch origin {branch} main && \ + mkdir -p .worktrees && \ + worktree-rm fix-{name} 2>/dev/null || true && \ + git worktree prune && \ + git worktree add .worktrees/fix-{name} origin/{branch} && \ + cd .worktrees/fix-{name} && \ + pnpm install --frozen-lockfile && \ + BEHIND=$(worktree-git rev-list --count HEAD..origin/main) && \ + if [ "$BEHIND" -gt 0 ]; then \ + worktree-git merge origin/main -m "Merge branch 'main'" --no-edit || \ + (worktree-git merge --abort && echo "Conflicts, continuing without merge"); \ + fi + +# File edits (auto-approve in .worktrees/) + +# Commit + push (one approval) — use HEAD:{branch} for detached worktrees +cd .worktrees/fix-{name} && \ + pnpm run format && \ + worktree-git add -A && \ + worktree-git commit -m "fix: message" && \ + worktree-git push origin HEAD:{branch} + +# Create/update PR +# gh pr create --draft --title "fix: title" --body "description" + +# Cleanup +worktree-rm fix-{name} +``` + +## Mandatory Functions + +**`worktree-git` and `worktree-rm` are auto-loaded via the Cursor preToolUse hook** (`.cursor/hooks/preload-shell-functions.sh`). They are available in every Shell tool call automatically. + +If they're not available, verify: + +```bash +type worktree-git &>/dev/null || echo "ERROR: worktree-git not loaded - check hook" +type worktree-rm &>/dev/null || echo "ERROR: worktree-rm not loaded - check hook" +``` + +## Auto-Approval for Speed + +**`worktree-git` and `worktree-rm` should be auto-approved in Cursor settings**, meaning they run without requiring user intervention. This is safe because they only work inside `.worktrees/` directories. + +| Command | User Approval Required | Why | +| -------------- | ---------------------- | ---------------------------------- | +| `worktree-git` | No (auto-approved) | Safe - only works in `.worktrees/` | +| `worktree-rm` | No (auto-approved) | Safe - only works in `.worktrees/` | +| `tools-git` | No (auto-approved) | Safe - only works in tools repo | +| `tools-rm` | No (auto-approved) | Safe - only works in tools repo | +| `git` | Yes | Could modify main workspace | +| `rm` | Yes | Could delete important files | + +**Always use `worktree-git` and `worktree-rm` in worktrees to maximize speed.** + +## Important: `tools-git` vs `worktree-git` + +- **`tools-git`** -- for git operations in the **main workspace**. Checks that `basename` of the repo root is `tools`. **Will NOT work in worktrees** because `git rev-parse --show-toplevel` returns the worktree path (e.g., `.worktrees/fix-foo`). +- **`worktree-git`** -- for git operations **inside `.worktrees/`** directories. Checks that `$PWD` contains `/.worktrees/`. + +Never mix them up. Use `tools-git` in the main workspace, `worktree-git` in worktrees. + +## Troubleshooting + +### `worktree-git` fails + +Functions are auto-loaded via the preToolUse hook. If not working: + +```bash +source .cursor/hooks/shell-functions/worktree-git.sh +``` + +### pnpm install fails + +```bash +cd .worktrees/fix-{name} && \ + worktree-rm node_modules && \ + pnpm install --frozen-lockfile +``` + +### Worktree already exists + +```bash +worktree-rm fix-{name} +git worktree prune +git worktree add .worktrees/fix-{name} origin/{branch} +``` + +### Merge conflicts + +```bash +cd .worktrees/fix-{name} && \ + worktree-git merge origin/main && \ + # resolve conflicts... + worktree-git add -A && \ + worktree-git commit -m "fix: resolve merge conflicts" +``` + +### Push from detached worktree is stale + +If `worktree-git push origin {branch}` pushes old commits, the worktree is detached and the push resolved the branch ref from the main workspace. Always use the `HEAD:` refspec: + +```bash +worktree-git push origin HEAD:{branch} +``` + +### Build errors across packages + +Never run `pnpm run build` at the root if you only changed one package. Build in dependency order: + +```bash +pnpm run --dir packages/utils build # if utils changed +pnpm run --dir packages/sdk build # if sdk changed (depends on utils) +pnpm run --dir packages/cli build # if cli changed (depends on sdk + utils) +``` + +### Pre-commit hook failures in worktree + +The husky hooks may not be installed in worktrees. If `git commit` fails with hook errors, either: + +1. Run `pnpm run format && pnpm run lint` manually before committing +2. Use `worktree-git commit --no-verify -m "message"` (only if you've already run quality checks) diff --git a/.gitignore b/.gitignore index 2ba7ee91..34105019 100644 --- a/.gitignore +++ b/.gitignore @@ -7,4 +7,5 @@ mise.local.lock mise.local.toml node_modules *.tsbuildinfo +.worktrees secret.env \ No newline at end of file From bdc29e1fa470125186d312a1765970a36c41388b Mon Sep 17 00:00:00 2001 From: Michael Farrell Date: Thu, 9 Apr 2026 17:11:01 -0700 Subject: [PATCH 02/12] UpdateS --- .cursor/agents/consent-triage-inspector.md | 123 ++++++++ .cursor/agents/consent-triage-researcher.md | 94 ++++++ .cursor/rules/commit-and-push.mdc | 18 ++ .cursor/skills/consent-triage/SKILL.md | 256 ++++++++++++++++ .../skills/consent-triage/docs-reference.md | 99 ++++++ .gitignore | 3 +- .../src/prompts/consent_inspect_site.ts | 287 ++++++++++++++++++ .../src/prompts/consent_research_tracker.ts | 147 +++++++++ .../src/prompts/consent_triage.ts | 139 +++++++++ .../mcp-server-consent/src/prompts/index.ts | 15 + .../src/resources/classification_guide.ts | 63 ++++ .../mcp-server-consent/src/resources/index.ts | 13 + packages/mcp/mcp-server-core/src/index.ts | 9 + .../mcp/mcp-server-core/src/prompts/types.ts | 47 +++ .../mcp-server-core/src/resources/types.ts | 17 ++ .../src/server/create-server.ts | 104 ++++++- 16 files changed, 1428 insertions(+), 6 deletions(-) create mode 100644 .cursor/agents/consent-triage-inspector.md create mode 100644 .cursor/agents/consent-triage-researcher.md create mode 100644 .cursor/skills/consent-triage/SKILL.md create mode 100644 .cursor/skills/consent-triage/docs-reference.md create mode 100644 packages/mcp/mcp-server-consent/src/prompts/consent_inspect_site.ts create mode 100644 packages/mcp/mcp-server-consent/src/prompts/consent_research_tracker.ts create mode 100644 packages/mcp/mcp-server-consent/src/prompts/consent_triage.ts create mode 100644 packages/mcp/mcp-server-consent/src/prompts/index.ts create mode 100644 packages/mcp/mcp-server-consent/src/resources/classification_guide.ts create mode 100644 packages/mcp/mcp-server-consent/src/resources/index.ts create mode 100644 packages/mcp/mcp-server-core/src/prompts/types.ts create mode 100644 packages/mcp/mcp-server-core/src/resources/types.ts diff --git a/.cursor/agents/consent-triage-inspector.md b/.cursor/agents/consent-triage-inspector.md new file mode 100644 index 00000000..1e1fd189 --- /dev/null +++ b/.cursor/agents/consent-triage-inspector.md @@ -0,0 +1,123 @@ +--- +name: consent-triage-inspector +description: >- + Inspects how trackers load on a live website using Chrome DevTools MCP. + Use when investigating how a data flow or cookie gets onto a page -- + whether it's a direct script, loaded via tag manager, in an iframe, + or dynamically injected. Checks ad infrastructure, consent state, and + airgap classification. +model: fast +readonly: true +is_background: true +--- + +You are a web inspector specialist. Your job is to analyze how trackers +load on live websites using Chrome DevTools MCP tools (navigate, evaluate). + +## Input + +You will receive: + +- A target site URL with regime override parameters +- A list of tracker domains to look for + +## Important: Platform vs Client Sites + +The bundle name (e.g. "acme-platform") may be a platform provider, not +the site with actual trackers. If the primary domain is a corporate landing +page without ad trackers, find a real client site from links on the homepage +and use that instead. + +## Setup + +### 1. Navigate with debug overrides + +Use the Chrome DevTools `navigate` tool with the override URL: +`{site}/#tcm-regime={regime}&tcm-prompt=Hidden&log=*` + +### 2. Verify consent state + +Evaluate JS to check `airgap.getRegimes()`, `airgap.getConsent().purposes`, +and `airgap.getRegimePurposes()`. All purposes should be `true` or `"Auto"`. + +If not, opt in manually: + +```javascript +airgap.optIn(Object.fromEntries(airgap.getRegimePurposes().map((p) => [p, true]))); +``` + +## Investigation + +Run the following checks via JS evaluation in DevTools. Write your own +snippets or adapt from the `consent-inspect-site` MCP prompt methodology. + +### 3. Performance entries + +Use `performance.getEntriesByType('resource')` and filter for each tracker +domain. Record URL, initiator type, duration, and transfer size. + +### 4. Search page HTML + +Search `document.documentElement.outerHTML` for each tracker domain. Capture +surrounding context (100 chars before/after) to understand placement. + +### 5. Ad infrastructure + +Find ad-related `