Skip to content
Back to skills

Docs Watch

BSecurity

Monitor Claude Code documentation for new features and changes relevant to the

  • 8 stars
  • 0 votes
  • 0 copies
  • 0 views
  • Added November 5, 2026
developmentpythongoshellbashgitapidocumentation

Works with

  • claude code
  • cursor
  • vscode
  • terminal
  • cli
  • api
  • mcp

Security analysis

B84/100
  • mediumUses curl or wget to download content
  • criticalSends environment variables or credentials to an external URL

Pro scans all 4 files and shows the line behind each finding

Scanned November 5, 2026

npx -y skills add nightgauge/nightgauge --skill docs-watch --agent claude-code

Installs into .claude/skills of the current project.

Are you the author of Docs Watch?

Add the live security badge to your README. It updates with every re-scan.

Security grade badge for Docs Watch
[![Security: B — Skills Directory](https://www.skillsdirectory.com/api/skills/nightgauge-docs-watch/badge)](https://www.skillsdirectory.com/skills/nightgauge-docs-watch)

More formats (shields.io, HTML) on the badges page. Keep it an A: scan every change in CI with Pro.

Download with Pro
SKILL.md
---
name: docs-watch
description: Monitor Claude Code documentation for new features and changes relevant to the
  pipeline. Use when checking for Claude Code updates or when a new CC version is
  released.
license: Apache-2.0
metadata:
  author: nightgauge
  version: "1.0.0"
  source: https://github.com/nightgauge/nightgauge
allowed-tools: Bash Read Write Edit Glob Grep
disable-model-invocation: true
---

# Nightgauge Docs Watch

## Description

Monitors Claude Code documentation at `https://code.claude.com/docs/` for new
pages, removed pages, and content changes. Compares the current state against a
locally stored snapshot and categorizes findings by relevance to the
Nightgauge pipeline.

**Use Cases:**

- Detecting new Claude Code features relevant to the pipeline
- Tracking documentation changes after a Claude Code release
- Auto-creating GitHub issues for high-relevance findings

**When to Use:**

- After a new Claude Code version is released
- On a periodic cadence (weekly or bi-weekly)
- When you suspect new features may be available

## Invocation

| Tool        | Command                                 |
| ----------- | --------------------------------------- |
| Claude Code | `/nightgauge:docs-watch [options]`      |
| Copilot     | Invoke via Agent Skills extension       |
| Cursor      | Run via Agent Skills or direct SKILL.md |
| Headless    | `claude -p "/nightgauge:docs-watch"`    |

## Arguments

| Argument                     | Description                                          | Default  |
| ---------------------------- | ---------------------------------------------------- | -------- |
| `--create-issues`            | Auto-create GitHub issues for high-relevance changes | `false`  |
| `--dry-run`                  | Show what issues would be created without creating   | `false`  |
| `--force-refresh`            | Re-fetch all pages, not just new/changed ones        | `false`  |
| `--correlate-releases`       | Cross-reference changes with Claude Code releases    | `true`\* |
| `--enrich-assessments`       | Update existing assessment files with doc links      | `false`  |
| `--skip-release-correlation` | Disable release correlation (e.g., for offline CI)   | `false`  |

\*Enabled by default when this checkout's `release-watch/last-seen-claude-code.json` exists
(`nightgauge layout path checkout release-watch/last-seen-claude-code.json`); disabled if missing.

### Examples

```bash
# Check for documentation changes (default: compare against last snapshot)
/nightgauge:docs-watch

# Check and auto-create issues for high-relevance changes
/nightgauge:docs-watch --create-issues

# Preview what issues would be created
/nightgauge:docs-watch --create-issues --dry-run

# Force re-fetch all pages and rebuild snapshot
/nightgauge:docs-watch --force-refresh

# Check changes and correlate with release versions
/nightgauge:docs-watch --correlate-releases

# Enrich existing assessment files with documentation context
/nightgauge:docs-watch --enrich-assessments
```

---

## Prerequisites

- `curl` installed (for fetching documentation pages)
- `sha256sum` or `shasum` installed (for content hashing)
- `jq` installed (for JSON processing)
- `gh` CLI installed and authenticated (only for `--create-issues`)
- Git repository with `.nightgauge/` directory

**CRITICAL**: This skill runs headless. Do NOT use AskUserQuestion. Make
autonomous decisions or fail with clear error.

---

## Workflow

### Phase 0: Environment Preflight

### Phase 0: Environment Preflight

**PURPOSE**: Verify the nightgauge binary is available and the environment
is healthy before executing any pipeline stage. Skills halt immediately with
actionable instructions when this check fails.

Three-layer check:

1. **Binary discovery** — shell-level, runs before invoking the binary
2. **Doctor check** — binary-level, full environment health via `doctor --json`
3. **Project environment** — the checks the project declares under
   `preflight.environment` (#101); opt-in, silent when none are declared

The binary discovery cascade below is **provider-neutral** (issue 4029): it works
identically under every adapter (Claude, Codex, Gemini, …) and references no
VSCode-extension-specific path. The host that spawns the skill (the VSCode
extension's `skillRunner`, or the Go `nightgauge` binary in auto/CLI mode)
resolves the binary authoritatively and exports `NIGHTGAUGE_BIN`, which
this cascade honors first. The remaining steps (PATH, the development-only repo
`bin/` and worktree canonical-repo `bin/`, `~/go/bin`) are fallbacks for direct
or terminal invocation.

This intentionally **diverges** from
`claude-plugins/nightgauge/hooks/lib/guard.sh` (issue 3262 → issue 4029): guard.sh is
Claude-Code-only and retains a trailing `~/.vscode/extensions/...` glob to serve
the standalone-terminal-Claude case (where no host exports `NIGHTGAUGE_BIN`).
Skills must stay portable Markdown and cannot reference that VSCode-only path —
so the shared resolution order (`NIGHTGAUGE_BIN` → PATH → gated repo → gated
canonical → `~/go/bin`) stays in sync, and only guard.sh carries the extra Claude-only
fallback. The `nightgauge preflight skill-portability` gate enforces that
no skill reintroduces a `.vscode/extensions` path, and that every cascade keeps
the `NIGHTGAUGE_DEV_REPO_BIN` gate (#2758): a foreign repository's
`bin/nightgauge` must never run.

After the cascade resolves `$BINARY`, the preflight prepends
`dirname($BINARY)` to `PATH` so subsequent bare `nightgauge ...`
invocations later in the same skill body resolve through PATH rather than
failing with "command not found" (issue 3262).

```bash
# Layer 1: provider-neutral binary discovery (NIGHTGAUGE_BIN → PATH → gated repo
# bin → gated canonical-repo bin → ~/go/bin). No VSCode-extension path — see issue 4029.
# Repo bins need NIGHTGAUGE_DEV_REPO_BIN=1 and a verified core checkout (#2758).
BINARY="${NIGHTGAUGE_BIN:-}"
[ -n "$BINARY" ] && [ ! -x "$BINARY" ] && BINARY=""
[ -z "$BINARY" ] && BINARY=$(command -v nightgauge 2>/dev/null || echo "")
if [ -z "$BINARY" ] && [ "${NIGHTGAUGE_DEV_REPO_BIN:-}" = "1" ]; then
  for NG_ROOT in "$(git rev-parse --show-toplevel 2>/dev/null)" \
    "$(cd "$(git rev-parse --git-common-dir 2>/dev/null)/.." 2>/dev/null && pwd)"; do
    [ -x "$NG_ROOT/bin/nightgauge" ] || continue
    grep -qx 'module github.com/nightgauge/nightgauge' "$NG_ROOT/go.mod" 2>/dev/null || continue
    NG_ORIGIN="$(git -C "$NG_ROOT" config --get remote.origin.url 2>/dev/null)"
    case "${NG_ORIGIN%.git}" in
      git@github.com:nightgauge/nightgauge | https://github.com/nightgauge/nightgauge | \
        ssh://git@github.com/nightgauge/nightgauge) BINARY="$NG_ROOT/bin/nightgauge"; break ;;
    esac
  done
fi
[ -z "$BINARY" ] && [ -x "$HOME/go/bin/nightgauge" ] && BINARY="$HOME/go/bin/nightgauge"
if [ -z "$BINARY" ]; then
  echo "ERROR: nightgauge binary not found via NIGHTGAUGE_BIN, PATH or ~/go/bin" >&2
  echo "" >&2
  echo "Install via: go install github.com/nightgauge/nightgauge/cmd/nightgauge@latest" >&2
  echo "Or download from: https://github.com/nightgauge/nightgauge/releases" >&2
  echo "Run \`nightgauge doctor\` after installing to verify your environment." >&2
  exit 1
fi
# Make the resolved binary callable via bare `nightgauge ...` later in this skill body (issue 3262).
export PATH="$(dirname "$BINARY"):$PATH"

# Per-repo GitHub token: export GH_TOKEN so every `gh` call in this skill
# authenticates as the *current repo's* configured user instead of the machine-
# global `gh auth` active account. `forge auth token` resolves per-repo (config
# github_user/token → GITHUB_TOKEN → `gh auth token --user <github_user>`), so
# concurrent sessions/workspaces owned by different GitHub users each use their
# own token. GH_TOKEN outranks GITHUB_TOKEN and the gh keyring. Only resolve when
# unset (an upstream env value — e.g. the VSCode extension's terminal env —
# wins); never fail preflight over it. Mirrors hooks/lib/guard.sh (sync, issue 3262).
if [ -z "${GH_TOKEN:-}" ]; then
  IB_REPO_TOKEN="$("$BINARY" forge auth token 2>/dev/null || true)"
  if [ -n "$IB_REPO_TOKEN" ]; then
    export GH_TOKEN="$IB_REPO_TOKEN"
    export GITHUB_TOKEN="${GITHUB_TOKEN:-$IB_REPO_TOKEN}"
  fi
  unset IB_REPO_TOKEN
fi

# Layer 2: full environment health check via doctor --json
DOCTOR_RESULT=$("$BINARY" doctor --json 2>/dev/null)
DOCTOR_EXIT=$?
if [ "$DOCTOR_EXIT" -eq 2 ]; then
  echo "ERROR: Environment check failed — nightgauge doctor reports broken environment." >&2
  FAILED_CHECKS=$(printf '%s\n' "$DOCTOR_RESULT" | jq -r '.failed_checks[]? // empty' 2>/dev/null)
  if [ -n "$FAILED_CHECKS" ]; then
    echo "Failing check(s): $(echo "$FAILED_CHECKS" | tr '\n' ',' | sed 's/,$//')" >&2
  fi
  printf '%s\n' "$DOCTOR_RESULT" | jq -r '.errors[]' >&2 2>/dev/null || true
  INSTALL_MSG=$(printf '%s\n' "$DOCTOR_RESULT" | jq -r '.install_instructions // empty' 2>/dev/null)
  [ -n "$INSTALL_MSG" ] && echo "$INSTALL_MSG" >&2
  exit 1
fi
if [ "$DOCTOR_EXIT" -eq 1 ]; then
  echo "WARNING: Environment has non-critical issues:" >&2
  printf '%s\n' "$DOCTOR_RESULT" | jq -r '.warnings[]' >&2 2>/dev/null || true
  # Continue — warnings do not block skill execution
fi

# Layer 3: project-declared environment checks (preflight.environment, #101).
# Opt-in: with none declared this prints nothing and continues. The verb
# writes one `[environment-halt]` line per failed check, with its remedy.
if ! "$BINARY" preflight environment >/dev/null; then
  echo "[environment-halt] Project environment check failed — fix the environment, not the code." >&2
  exit 1
fi
```

**Exit codes from `nightgauge doctor`**:

| Code | Meaning                         | Skill behavior                          |
| ---- | ------------------------------- | --------------------------------------- |
| 0    | Healthy                         | Continue                                |
| 1    | Degraded (warnings only)        | Continue with warning printed to stderr |
| 2    | Broken (required checks failed) | Halt immediately with error details     |

**Layer 3** halts the stage when any check declared under
`preflight.environment` in the workspace's `.nightgauge/config.yaml` fails
(exit 1) or the declaration is invalid (exit 2). The failure is in the
operator's environment, not the code: do not edit code to work around it, do
not run the remedy yourself, and end the stage. The pipeline records it as
`environment_halt` and holds the issue for an operator.

Do not theorize about the cause beyond what is printed here — the failing
check name (`failed_checks`) and its `.errors[]` message are the complete
diagnosis (#277).


---

### Phase 1: Fetch Current Documentation Index

#### Step 1.1: Fetch the docs index

Fetch the current page list from Claude Code documentation:

```bash
curl -s https://code.claude.com/docs/llms.txt > /tmp/docs-watch-index.txt
```

If the fetch fails, error with a clear message:

```bash
if [ ! -s /tmp/docs-watch-index.txt ]; then
  echo "ERROR: Failed to fetch docs index from https://code.claude.com/docs/llms.txt"
  echo "  Check network connectivity and try again."
  exit 1
fi
```

#### Step 1.2: Extract page URLs

Parse the index to extract all documentation page URLs. The `llms.txt` file
contains URLs (one per line or embedded in text). Extract all URLs matching
`https://code.claude.com/docs/`:

```bash
grep -oE 'https://code\.claude\.com/docs/[a-zA-Z0-9/_-]+' /tmp/docs-watch-index.txt \
  | sort -u > /tmp/docs-watch-urls.txt
PAGE_COUNT=$(wc -l < /tmp/docs-watch-urls.txt | tr -d ' ')
echo "Found ${PAGE_COUNT} documentation pages"
```

---

### Phase 2: Load Stored Snapshot

#### Step 2.1: Load or initialize snapshot

```bash
# This checkout's doc-snapshots/ inside the git directory (ADR-024 § 7):
# read by path, written only through `nightgauge layout write checkout`.
SNAPSHOT_NAME="doc-snapshots/index.json"
SNAPSHOT_FILE="$(nightgauge layout path checkout "$SNAPSHOT_NAME")"

if [ ! -f "$SNAPSHOT_FILE" ]; then
  echo '{"last_check":"1970-01-01T00:00:00Z","page_count":0,"pages":{}}' \
    | jq '.' | nightgauge layout write checkout "$SNAPSHOT_NAME" > /dev/null
  echo "Initialized new snapshot file at ${SNAPSHOT_FILE}"
fi
```

Read the snapshot into a temp file for comparison:

```bash
cp "$SNAPSHOT_FILE" /tmp/docs-watch-snapshot.json
LAST_CHECK=$(jq -r '.last_check' /tmp/docs-watch-snapshot.json)
echo "Last check: ${LAST_CHECK}"
```

---

### Phase 3: Compare Index Against Snapshot

#### Step 3.1: Detect new, existing, and removed pages

```bash
# Extract known URLs from snapshot
jq -r '.pages | keys[]' /tmp/docs-watch-snapshot.json | sort > /tmp/docs-watch-known.txt

# New pages: in current but not in snapshot
comm -23 /tmp/docs-watch-urls.txt /tmp/docs-watch-known.txt > /tmp/docs-watch-new.txt

# Removed pages: in snapshot but not in current
comm -13 /tmp/docs-watch-urls.txt /tmp/docs-watch-known.txt > /tmp/docs-watch-removed.txt

# Existing pages: in both
comm -12 /tmp/docs-watch-urls.txt /tmp/docs-watch-known.txt > /tmp/docs-watch-existing.txt

NEW_COUNT=$(wc -l < /tmp/docs-watch-new.txt | tr -d ' ')
REMOVED_COUNT=$(wc -l < /tmp/docs-watch-removed.txt | tr -d ' ')
EXISTING_COUNT=$(wc -l < /tmp/docs-watch-existing.txt | tr -d ' ')

echo "New pages: ${NEW_COUNT}"
echo "Removed pages: ${REMOVED_COUNT}"
echo "Existing pages to check: ${EXISTING_COUNT}"
```

---

### Phase 4: Fetch and Hash Content

Use the snapshot-diff script to detect content changes:

```bash
nightgauge docs snapshot-diff \
  --snapshot /tmp/docs-watch-snapshot.json \
  --urls /tmp/docs-watch-urls.txt \
  --json \
  > /tmp/docs-watch-diff.json
```

The script outputs JSON with `new`, `changed`, and `removed` arrays. Each entry
includes the URL and content hash.

If `--force-refresh` is set, treat ALL pages as new (ignore existing hashes).

---

### Phase 5: Categorize Findings by Relevance

Read the reference file for categorization criteria:

```bash
REFERENCE_FILE="${SKILL_DIR}/reference.md"
```

Apply relevance scoring based on page URL path segments. Use the categories
defined in `reference.md`:

**High relevance** (directly affects pipeline operation):

- sub-agents, skills, hooks, headless, mcp, plugins, permissions, settings,
  sdk, agent, automation, custom-commands

**Medium relevance** (useful for pipeline improvement):

- cli-reference, common-workflows, best-practices, tools-reference,
  github-actions, configuration, memory, context

**Low relevance** (informational only):

- authentication, data-usage, legal, troubleshooting, desktop, chrome, voice,
  pricing, changelog

For each new or changed page:

1. Fetch the page content: `curl -s <url>`
2. Generate sha256 hash of the content
3. Match URL path segments against relevance categories
4. Assign relevance: `high`, `medium`, or `low`
5. Extract a brief summary of the page content (first 200 chars or title)

---

### Phase 5.5: Release Correlation (Optional)

Only executes when `--skip-release-correlation` is not set AND this checkout's
`release-watch/last-seen-claude-code.json` exists.

#### Step 5.5.1: Load release state and recent reports

```bash
RELEASE_STATE_FILE="$(nightgauge layout path checkout release-watch/last-seen-claude-code.json)"
REPORTS_DIR="$(nightgauge layout path checkout release-watch/reports)"
export RELEASE_STATE_FILE REPORTS_DIR

if [ ! -f "$RELEASE_STATE_FILE" ]; then
  echo "Release state not found; skipping correlation"
  CORRELATE_RELEASES=false
else
  CURRENT_VERSION=$(jq -r '.version // .full_tag' "$RELEASE_STATE_FILE" 2>/dev/null | sed 's/^v//')
  CORRELATE_RELEASES=true
  echo "Will correlate changes with releases (current: ${CURRENT_VERSION})"
fi
```

#### Step 5.5.2: Correlate each detected change

For each new or changed page in the diff results, attempt correlation by:

1. **Timestamp matching** — If the change was detected within ±7 days of a release date,
   correlate with that release (confidence: high if exact match, medium if 3-7 days)
2. **URL pattern matching** — If the page URL contains version references (e.g.,
   `/docs/v2.1/...` or `/docs/2.1.81/...`), extract the version
3. **Content matching** — If the page content includes release notes or changelog,
   search for feature keywords from recent releases

```bash
python3 << 'PYTHON_EOF'
import json
import os
import re
from datetime import datetime, timedelta

def correlate_change(page_url, change_type, last_seen_iso, release_version, reports_dir):
    """Attempt to correlate a documentation change with a release.

    Returns:
    {
        'correlated_release': 'version string or null',
        'correlation_confidence': 'high|medium|low',
        'correlation_method': 'timestamp_match|url_pattern|content_match|none'
    }
    """
    result = {
        'correlated_release': None,
        'correlation_confidence': 'low',
        'correlation_method': 'none'
    }

    # Parse timestamps
    try:
        if last_seen_iso:
            change_date = datetime.fromisoformat(last_seen_iso.replace('Z', '+00:00'))
        else:
            return result
    except:
        return result

    # Method 1: URL pattern matching
    url_pattern = r'/(v?\d+\.\d+(?:\.\d+)?)[/]?'
    match = re.search(url_pattern, page_url)
    if match:
        version_from_url = match.group(1).lstrip('v')
        result['correlated_release'] = version_from_url
        result['correlation_confidence'] = 'medium'
        result['correlation_method'] = 'url_pattern'
        return result

    # Method 2: Timestamp matching (within ±7 days of release)
    # Load release reports to find nearest release date
    if os.path.isdir(reports_dir):
        try:
            reports = [f for f in os.listdir(reports_dir) if f.endswith('.json')]
            reports.sort(reverse=True)  # newest first

            for report_file in reports[:5]:  # check last 5 reports
                try:
                    with open(os.path.join(reports_dir, report_file)) as f:
                        report = json.load(f)

                    for release in report.get('releases', []):
                        release_date_str = release.get('published_at', '')
                        if not release_date_str:
                            continue

                        try:
                            release_date = datetime.fromisoformat(release_date_str.replace('Z', '+00:00'))
                            time_diff = abs((change_date - release_date).days)

                            if time_diff <= 1:  # exact day match
                                result['correlated_release'] = release['version']
                                result['correlation_confidence'] = 'high'
                                result['correlation_method'] = 'timestamp_match'
                                return result
                            elif time_diff <= 7:  # within 7 days
                                result['correlated_release'] = release['version']
                                result['correlation_confidence'] = 'medium'
                                result['correlation_method'] = 'timestamp_match'
                                # Don't return; keep looking for better match
                        except:
                            continue
                except:
                    continue
        except:
            pass

    return result

# Load current diff results
with open('/tmp/docs-watch-diff.json') as f:
    diff = json.load(f)

# Load release state
with open(os.environ['RELEASE_STATE_FILE']) as f:
    release_state = json.load(f)

current_version = release_state.get('version', release_state.get('full_tag', '')).lstrip('v')
reports_dir = os.environ['REPORTS_DIR']

# Enrich each change with correlation data
for entry_list in [diff.get('new', []), diff.get('changed', [])]:
    for entry in entry_list:
        correlation = correlate_change(
            entry['url'],
            'new' if entry in diff.get('new', []) else 'changed',
            entry.get('last_seen'),
            current_version,
            reports_dir
        )
        entry.update(correlation)

# Save enriched diff
with open('/tmp/docs-watch-diff-correlated.json', 'w') as f:
    json.dump(diff, f, indent=2)

print(f"Correlated {len(diff.get('new', [])) + len(diff.get('changed', []))} changes with releases")
PYTHON_EOF

# Use correlated diff for subsequent phases
cp /tmp/docs-watch-diff-correlated.json /tmp/docs-watch-diff.json
```

---

### Phase 6: Generate Report

#### Step 6.1: Build findings summary

```
CLAUDE CODE DOCS WATCH REPORT
═══════════════════════════════════════════════════════════
Check Date:    2026-03-19
Last Check:    2026-03-12
Pages Tracked: 72

CHANGES DETECTED
───────────────────────────────────────────────────────────
  New Pages:     3
  Changed Pages: 5
  Removed Pages: 0

HIGH RELEVANCE (action recommended)
───────────────────────────────────────────────────────────
  [NEW] https://code.claude.com/docs/sub-agents
    → New sub-agent capabilities may affect pipeline stage isolation

  [CHANGED] https://code.claude.com/docs/hooks
    → Content hash changed — review for new hook types

MEDIUM RELEVANCE (review recommended)
───────────────────────────────────────────────────────────
  [CHANGED] https://code.claude.com/docs/cli-reference
    → CLI flags may have new options for headless execution

LOW RELEVANCE (informational)
───────────────────────────────────────────────────────────
  [NEW] https://code.claude.com/docs/troubleshooting-faq
    → New troubleshooting page added

───────────────────────────────────────────────────────────
Run with --create-issues to auto-create GitHub issues for high-relevance findings.
```

#### Step 6.2: Write JSON report

Write structured results to `/tmp/docs-watch-report.json`:

```json
{
  "check_date": "ISO-8601",
  "last_check": "ISO-8601",
  "page_count": 72,
  "new_pages": [],
  "changed_pages": [],
  "removed_pages": [],
  "findings": [
    {
      "url": "https://code.claude.com/docs/sub-agents",
      "type": "new",
      "relevance": "high",
      "summary": "Brief description of the page content",
      "correlated_release": "2.1.81",
      "correlation_confidence": "high",
      "correlation_method": "timestamp_match"
    }
  ],
  "release_correlation": {
    "enabled": true,
    "current_release": "2.1.81",
    "correlated_count": 3,
    "high_confidence_count": 1,
    "medium_confidence_count": 1,
    "low_confidence_count": 1
  }
}
```

---

### Phase 7: Update Snapshot

Write the updated snapshot with current hashes:

```bash
# Build updated snapshot from diff results
python3 -c "
import json, sys
from datetime import datetime

# Load existing snapshot
with open('/tmp/docs-watch-snapshot.json') as f:
    snapshot = json.load(f)

# Load diff results
with open('/tmp/docs-watch-diff.json') as f:
    diff = json.load(f)

# Update pages with new/changed hashes and correlation data
for entry in diff.get('new', []) + diff.get('changed', []):
    page_data = {
        'hash': entry['hash'],
        'last_seen': datetime.utcnow().isoformat() + 'Z',
        'relevance': entry.get('relevance', 'low')
    }

    # Add correlation data if present (optional, for backward compatibility)
    if entry.get('correlated_release'):
        page_data['correlated_release'] = entry.get('correlated_release')
        page_data['correlation_confidence'] = entry.get('correlation_confidence', 'low')
        page_data['correlation_method'] = entry.get('correlation_method', 'none')

    snapshot['pages'][entry['url']] = page_data

# Remove deleted pages
for entry in diff.get('removed', []):
    snapshot['pages'].pop(entry['url'], None)

# Update metadata
snapshot['last_check'] = datetime.utcnow().isoformat() + 'Z'
snapshot['page_count'] = len(snapshot['pages'])

with open('/tmp/docs-watch-snapshot-updated.json', 'w') as f:
    json.dump(snapshot, f, indent=2)

print(f'Snapshot updated: {len(snapshot[\"pages\"])} pages tracked')
"
nightgauge layout write checkout "$SNAPSHOT_NAME" \
  --from /tmp/docs-watch-snapshot-updated.json > /dev/null
```

---

### Phase 8: Issue Creation (Optional)

Only executes when `--create-issues` is passed and `--dry-run` is false.

#### Step 8.1: Filter high-relevance findings

Only create issues for findings with `relevance: high`.

#### Step 8.2: Check for existing issues

```bash
gh issue list --search "claude-code-feature" --label "claude-code-feature" \
  --state open --json number,title
```

Skip creation if a matching issue already exists.

#### Step 8.3: Create issues

For each high-relevance finding:

```bash
gh issue create \
  --title "feat: Claude Code doc change — ${PAGE_NAME}" \
  --body "$(cat <<EOF
## Summary

A ${CHANGE_TYPE} was detected in Claude Code documentation that may affect the
Nightgauge pipeline.

**Page:** ${URL}
**Change Type:** ${CHANGE_TYPE}
**Relevance:** High
$(if [ -n "${CORRELATED_RELEASE}" ]; then echo "**Correlated Release:** v${CORRELATED_RELEASE} (${CORRELATION_CONFIDENCE} confidence)"; fi)

## Details

${SUMMARY}

## Action Items

- [ ] Review the documentation change
- [ ] Assess impact on pipeline stages
- [ ] Implement any necessary updates

## Release Correlation

This change was correlated with release v${CORRELATED_RELEASE} using ${CORRELATION_METHOD}.
See \`docs-watch\` Release Correlation feature in
[skills/nightgauge-docs-watch/correlation-reference.md](../../skills/nightgauge-docs-watch/correlation-reference.md)
for details on how correlation works and confidence scoring.

## Source

Auto-detected by \`/nightgauge:docs-watch\` on $(date -u +%Y-%m-%dT%H:%M:%SZ)
EOF
)" \
  --label "claude-code-feature,auto-detected"
```

For `--dry-run`, output what would be created without actually creating.

#### Step 8.4: Report created issues

Output list of created issues with numbers, titles, and URLs.

---

### Phase 9: Run Reflection

The snapshot (`index.json`) already tracks per-page state; this log tracks the
**per-run delta** so each run can report "since last check" at a glance.

```bash
SKILL_NAME="nightgauge-docs-watch"
RUN_LOG_CLASS="checkout"
RUN_LOG_NAME="doc-snapshots/runs.jsonl"
RUN_LOG="$(nightgauge layout path "$RUN_LOG_CLASS" "$RUN_LOG_NAME")"
```

### Run Reflection (memory across runs)

**PURPOSE**: Cadence skills (those run weekly/periodically) should remember their
own prior runs so each execution reports **deltas, not a full re-dump**. Without
memory, every run re-surfaces the same findings and can't say "what changed since
last time." This mirrors `nightgauge-release-watch`'s `last-seen-<provider>.json`.

> Set `RUN_LOG_CLASS`, `RUN_LOG_NAME` and
> `RUN_LOG="$(nightgauge layout path "$RUN_LOG_CLASS" "$RUN_LOG_NAME")"`
> **before** this section. The class is a per-clone class directory
> (`pipeline`, `plans`, `retros`, `logs`) or this checkout's directory
> (`checkout`), e.g. `RUN_LOG_CLASS="checkout"` with
> `RUN_LOG_NAME="doc-snapshots/runs.jsonl"`. Never `${CLAUDE_PLUGIN_DATA}`
> (single source of truth). Both directories are inside the git directory, so
> the log is read by path and appended through `nightgauge layout append`,
> never a redirect.

#### Step 1: Load the previous run

```bash
if [ -f "$RUN_LOG" ]; then
  PREV=$(tail -n 1 "$RUN_LOG")
  PREV_TS=$(printf '%s\n' "$PREV" | jq -r '.ts // "never"')
  echo "Last run: ${PREV_TS}"
else
  PREV='{}'
  PREV_TS="never"
  echo "No prior run — this is the first reflection (full report)."
fi
```

#### Step 2: Report deltas, not the whole world

Compare this run's key signals against `PREV` and lead the output with the
**delta**: what is newly appeared, newly resolved, or newly changed since
`PREV_TS`. Fall back to a full report only when `PREV_TS` is `never`. Examples of
delta signals by skill: new vs. resolved stale issues (backlog-groom), new /
changed / removed doc pages (docs-watch), friction-rate and health-trend
movement (continuous-improvement).

#### Step 3: Append this run (skip on `--dry-run`)

```bash
if [[ "$*" == *"--dry-run"* ]]; then
  echo "Dry-run: not appending to $RUN_LOG"
else
  NOW=$(date -u +%Y-%m-%dT%H:%M:%SZ)
  RECORD=$(jq -nc --arg ts "$NOW" --arg skill "$SKILL_NAME" \
        --argjson counts "${RUN_COUNTS:-{}}" --arg summary "${RUN_SUMMARY:-}" \
        '{ts:$ts, skill:$skill, counts:$counts, summary:$summary}')
  printf '%s\n' "$RECORD" | nightgauge layout append "$RUN_LOG_CLASS" "$RUN_LOG_NAME" >/dev/null
  echo "Appended run record to $RUN_LOG"
fi
```

**Schema** (one JSON object per line): `{ ts, skill, counts:{…}, summary }`.
`counts` holds the skill's headline metrics so the next run can diff them.


Set `RUN_COUNTS` (e.g. `{"new":N,"changed":N,"removed":N,"issues_created":N}`)
and `RUN_SUMMARY` from the Phase 6 report before the append step.

---

### Self-Assessment Epilogue

### Self-Assessment Epilogue

**PURPOSE**: Evaluate whether this skill's instructions matched reality during
this execution. This phase is **non-blocking** — skip entirely if any main phase
failed. A perfectly working skill produces **no output** from this phase.

> See [docs/SKILL_SELF_ASSESSMENT.md](https://github.com/nightgauge/nightgauge/blob/main/docs/SKILL_SELF_ASSESSMENT.md) for
> the full strategy, synthesis algorithm, and integration architecture.

#### When to Skip

- Any prior phase exited with an error before completing
- The skill was cancelled or timed out
- Running in `--dry-run` mode

#### Step 1: Evaluate Execution Friction

Review the execution that just completed. Answer these questions honestly:

1. **Command failures**: Did any command, script, or binary call in the skill
   instructions fail? (e.g., script not found, wrong flags, missing binary)
2. **Workarounds**: Did you have to deviate from the skill instructions to
   accomplish the goal? (e.g., used `gh` directly because a referenced script
   didn't exist, skipped a step that referenced a nonexistent file)
3. **Stale references**: Did any file path, function name, API endpoint, or tool
   referenced in the instructions not exist in the current codebase?
4. **Unclear instructions**: Were any instructions ambiguous enough that you had
   to guess at the intended behavior?
5. **Missing instructions**: Was there a significant step you had to figure out
   on your own that should have been documented in this skill?

**If ALL answers are "no" — write nothing and complete the skill normally.** The
goal is silence when everything works.

#### Step 2: Write Assessment Record

Only if friction was detected in Step 1. Write a single JSON file,
`assessments/{STAGE_NAME}-${ISSUE_NUMBER}.json` in the clone's pipeline state
directory, through the binary (JSON on stdin; never a direct file write):

```bash
ISSUE_NUMBER="${NIGHTGAUGE_ISSUE_NUMBER:-$(git branch --show-current | sed -n 's#^[^/]*/\([0-9]*\)-.*#\1#p')}"
: "${ISSUE_NUMBER:?set NIGHTGAUGE_ISSUE_NUMBER or check out the issue branch}"
nightgauge layout write pipeline "assessments/{STAGE_NAME}-${ISSUE_NUMBER}.json" <<'JSON'
{ ... the record below ... }
JSON
```

The assessment record MUST follow this schema:

```json
{
  "schema_version": "1",
  "skill": "{STAGE_NAME}",
  "skill_file": "skills/nightgauge-{STAGE_NAME}/SKILL.md",
  "issue_number": 42,
  "timestamp": "2026-03-10T14:30:00Z",
  "friction": [
    {
      "type": "command_failure",
      "severity": "high",
      "description": "hooks/lib/add-to-project.sh not found — script was deleted",
      "skill_line_hint": "claude-plugins/nightgauge/hooks/lib/add-to-project.sh <issue-number>",
      "actual_resolution": "Used gh api graphql to add issue to project board directly",
      "suggested_fix": "Replace with: nightgauge project add <issue-number>"
    }
  ]
}
```

**Friction types**: `command_failure`, `workaround`, `stale_reference`,
`unclear_instruction`, `missing_instruction`

**Severity levels**:

- `high` — instruction is **broken**. Required manual workaround to complete.
- `medium` — instruction is **misleading**. Agent adapted without user help.
- `low` — instruction is **suboptimal**. No functional impact.

**Rules**:

- **One record per execution** — multiple friction items go in the `friction`
  array, not separate files.
- **Be specific** — quote the exact instruction text that was wrong. Not "some
  commands didn't work" but "Step 5.2 calls `hooks/lib/add-to-project.sh` but
  this script was deleted in commit 65915701."
- **Suggest the fix** — every finding MUST include `suggested_fix` with the
  concrete SKILL.md change needed. Not "update the docs" but "replace
  `hooks/lib/add-to-project.sh <N>` with `nightgauge project add <N>`."
- **Don't invent friction** — only report issues you actually encountered during
  this execution. Do not speculate about potential problems.

#### Step 3: Validate and Complete

```bash
# Validate JSON if written
ISSUE_NUMBER="${NIGHTGAUGE_ISSUE_NUMBER:-$(git branch --show-current | sed -n 's#^[^/]*/\([0-9]*\)-.*#\1#p')}"
: "${ISSUE_NUMBER:?set NIGHTGAUGE_ISSUE_NUMBER or check out the issue branch}"
ASSESSMENT_FILE="$(nightgauge layout path pipeline "assessments/{STAGE_NAME}-${ISSUE_NUMBER}.json")"
if [ -f "$ASSESSMENT_FILE" ]; then
  python3 -m json.tool "$ASSESSMENT_FILE" > /dev/null 2>&1 || \
    echo "WARNING: Assessment record is not valid JSON" >&2
fi
```


---

## Error Handling

| Condition                             | Action                                   |
| ------------------------------------- | ---------------------------------------- |
| Network failure fetching docs index   | Error with connectivity instructions     |
| curl timeout on individual page       | Skip page, note in report                |
| Malformed snapshot JSON               | Re-initialize snapshot, warn user        |
| `gh` CLI not authenticated            | Error with `gh auth login` instructions  |
| `--create-issues` with no findings    | Output "No high-relevance changes found" |
| `--dry-run` without `--create-issues` | Warn, continue with analysis only        |
| sha256sum/shasum not found            | Try both, error if neither available     |
| jq not installed                      | Error with install instructions          |

---

## Pipeline Position

```
UTILITIES (not part of main pipeline)

/nightgauge:docs-watch
       ↑
  Use on regular cadence or after CC releases
  Reads:  https://code.claude.com/docs/llms.txt
  Reads:  checkout doc-snapshots/index.json
  Writes: checkout doc-snapshots/index.json
  Writes: /tmp/docs-watch-report.json (ephemeral)
```

This is a standalone utility skill. It does not affect pipeline state and can be
run at any time without interfering with active pipeline runs.

---

**Author:** nightgauge **License:** Apache-2.0

Files in this skill

  • SKILL.md34.6 KB
  • correlation-reference.md12.4 KB
  • reference.md9.1 KB
  • scripts/snapshot-diff.sh3.3 KB

Attribution

Is this your skill, or is something wrong with this listing? Request removal or report an issue. Author removals are honored within 72 hours.

Comments

Loading comments…

Newsletter

Get the weekly list of safe new skills, and the ones we pulled.