Skip to main content
ClaudeWave
Skill606 repo starsupdated today

watch

The watch skill automatically triggers actions when monitored file paths change or when marker comments like `@citadel:` appear in files. Use it to set up file sentinels that execute skills or commands in response to specific file modifications, rather than for one-off inspections or CI status monitoring on pull requests.

Install in Claude Code
Copy
git clone --depth 1 https://github.com/SethGammon/Citadel /tmp/watch && cp -r /tmp/watch/skills/watch ~/.claude/skills/watch
Then start a new Claude Code session; the skill loads automatically.

SKILL.md

# /watch -- File Sentinel

Use when the user wants automatic reactions to file changes or marker comments (`@citadel:`).
Do NOT use for one-off file inspection or tasks that need human judgment per file.

## Orientation

**Use when:** running a file sentinel that triggers a skill or command automatically when watched paths change.
**Don't use when:** monitoring CI status on a PR (use /pr-watch); running a one-shot verification check (use /verify).

## Default execution path (READ FIRST)

**`/watch start` does NOT call `CronCreate` by default.** Only pass `--remote`
to use Anthropic's routine system, and only after explicit user confirmation.
`CronCreate` counts against the **15 routine runs / 24h** cap — at the default
5-minute interval a watch exhausts the quota in under an hour.

### Default flow — `/watch start` (no `--remote` flag)
1. Do Steps 1 and 2 below (check existing watch, determine baseline commit).
2. **Skip Step 3** — do NOT call `CronCreate`. Leave `cronId: null` in state.
3. Write the state file (Step 4) with `status: "watching"`.
4. Output:
   ```
   Watch state created: .planning/watch-state.json
     Baseline: {commit hash, first 7 chars}

   To start real-time watching, run in a separate terminal:
     npm run watch:local

   For cloud-persistent polling (machine off, user away):
     /watch start --remote     (uses CronCreate, counts against 15/day cap)
   ```

### Opt-in routine flow — `/watch start --remote`
Only when `--remote` is explicitly passed:
1. Confirm: "This will use `CronCreate`, which counts against your 15 routine
   runs / 24h quota. At a 5-minute interval this exhausts the quota in under
   an hour. Continue? (y/N)"
2. On confirmation, run the full Step 1–5 protocol including `CronCreate`.

## Commands

| Command | Behavior |
|---|---|
| `/watch start` | Default: create state, prompt user to run `npm run watch:local` |
| `/watch start --remote` | Use `CronCreate` polling (counts against 15/day quota — requires confirmation) |
| `/watch start --interval {N}m` | Set poll interval for `--remote` mode (default: 5m) |
| `/watch stop` | Stop watching, tear down cron |
| `/watch status` | Show watch state, last scan time, pending actions |
| `/watch scan` | Run a single scan now (manual trigger) |

## Protocol

### /watch start

#### Step 1: Check for existing watch

1. Read `.planning/watch-state.json` if it exists
2. If `status` is `"watching"`:
   - Show current state: last scan time, interval, pending actions count
   - Ask: "A watch is already active. Stop it and start a new one?"
   - If yes: run `/watch stop` first, then continue
   - If no: abort

#### Step 2: Determine baseline commit

1. Run `git rev-parse HEAD` to get the current commit hash
2. If not a git repo: fall back to timestamp-based detection (store current
   time as `lastScanTime`, skip commit-based diffing)
3. Store this as `lastScanCommit`

#### Step 3: Create poll schedule (--remote only)

```
CronCreate:
  interval: "{N}m"  (default: 5m)
  command: "/watch scan"
```

Save the cron ID in the state file.

#### Step 4: Write state file

Write `.planning/watch-state.json`:

```json
{
  "status": "watching",
  "lastScanCommit": "abc1234",
  "lastScanTime": null,
  "interval": "5m",
  "cronId": "{id from step 3 or null}",
  "pendingActions": {},
  "processedMarkers": {},
  "stats": {
    "scansRun": 0,
    "markersFound": 0,
    "intakeItemsCreated": 0,
    "skillsDispatched": 0
  }
}
```

#### Step 5: Confirm (--remote only)

```
Watch started.
  Interval:  every {N}m
  Baseline:  {commit hash, first 7 chars}
  State:     .planning/watch-state.json
```

---

### /watch stop

1. Read `.planning/watch-state.json`. If missing or not `"watching"`: "No watch is active."
2. `CronDelete: {cronId}` — if cronId is missing or deletion fails, continue.
3. Update state: `"status": "stopped", "cronId": null`. Preserve all other fields.
4. Output:
   ```
   Watch stopped.
     Scans completed:      {stats.scansRun}
     Markers found:        {stats.markersFound}
     Intake items created: {stats.intakeItemsCreated}
     Skills dispatched:    {stats.skillsDispatched}
   ```

---

### /watch status

1. If `.planning/watch-state.json` missing: "No watch configured. Use `/watch start` to begin."
2. Output state fields: status, lastScanTime, lastScanCommit, interval, pendingActions.length, stats.
3. If `pendingActions` non-empty, list each: `[{action}] {file}:{line} -- {description}`

---

### /watch scan

#### Step 1: Load state

1. Read `.planning/watch-state.json`
2. If missing: create default state with `lastScanCommit` from `git rev-parse HEAD` and `status: "watching"`.

#### Step 2: Detect changed files

**Git mode (primary):**
1. `git diff --name-only {lastScanCommit} HEAD` (committed changes)
2. `git diff --name-only` (unstaged) and `git diff --name-only --cached` (staged)
3. Merge and deduplicate all three lists

**Fallback mode (no git):**
1. `find . -newer {timestamp_file} -type f`
2. Exclude `node_modules/`, `.git/`, `.planning/`, `dist/`, `build/`

If no files changed: update `lastScanTime` and `stats.scansRun`, exit early.

#### Step 3: Scan for marker comments

Search changed files for:

| Pattern | Languages |
|---|---|
| `// @citadel: {action} {description}` | JS, TS, Go, Rust, C, Java |
| `# @citadel: {action} {description}` | Python, Shell, YAML, Ruby |
| `/* @citadel: {action} {description} */` | CSS, multi-line C-style |
| `<!-- @citadel: {action} {description} -->` | HTML, Markdown |

**Action-to-skill mapping:**

| Action | Skill |
|---|---|
| `review` | `/review` |
| `test` | `/test-gen` |
| `fix` | `/systematic-debugging` |
| `document` | `/doc-gen` |
| `refactor` | `/refactor` |
| `todo` | intake item |

Unknown actions become intake items with the action preserved as metadata.

**Deduplication:** Every marker gets a stable identity hash: sha256 over `{file path}`, `{action}`, and the normalized marker text (trimmed, internal whitespace collapsed), joined with NUL separators and