Skip to content

feat(cli): add ctx7 update to refresh installed skills and rules - #2951

Open
fahreddinozcan wants to merge 3 commits into
masterfrom
ctx7-1879-skill-content-update-cli
Open

feat(cli): add ctx7 update to refresh installed skills and rules#2951
fahreddinozcan wants to merge 3 commits into
masterfrom
ctx7-1879-skill-content-update-cli

Conversation

@fahreddinozcan

@fahreddinozcan fahreddinozcan commented Jul 28, 2026

Copy link
Copy Markdown
Collaborator

Keeps skills and rules installed by ctx7 setup current as their upstream content changes, refreshing them automatically during ordinary commands and adding ctx7 update for manual control.

  • Adds skills/manifest.json, generated by scripts/generate-content-manifest.mjs, giving each skill and rule a revision, a minCliVersion, and per-file content hashes. A CI step regenerates it and fails on a dirty diff.
  • Resolves content from raw.githubusercontent.com against that manifest, verifying every file hash before accepting it. Falls back to the existing GitHub API download for skills and to the built-in constants for rules when unreachable.
  • Caches the manifest in cli-state.json behind a 24h TTL and records each install there by absolute path, with its revision, content hash, and file list.
  • Refreshes out-of-date content silently in the preAction hook, so ctx7 docs and ctx7 library self-heal. Guarded by a stale-tolerant lock file, since agents run these concurrently.
  • Detects locally modified skills and rules by hash and leaves them alone; ctx7 update --force overwrites.
  • Blocks revisions whose minCliVersion exceeds the running CLI and points at ctx7 upgrade.
  • Prunes files dropped upstream instead of leaving them behind, and clears install records on ctx7 remove.

Content freshness is decoupled from the npm release cycle: a merge to master reaches existing installs on their next check regardless of the installed CLI version.

Why auto-refresh

The rules and skills tell agents to invoke npx ctx7@latest docs ..., which always runs with stdout piped. A TTY-gated notice is invisible on that path, so the overwhelmingly common usage would never have been told to update and would never have updated. Refreshing in the natural flow is what actually closes the loop; ctx7 update exists for the cases auto-refresh deliberately will not touch.

Auto-refresh is silent, never fails the host command, and skips setup, update, remove, and upgrade. CTX7_NO_AUTO_UPDATE=1 disables it. Cost on the common path is one state-file read: on-disk hashing only happens for records whose revision already differs from the cached manifest, and the manifest itself is fetched at most once per day.

The interactive notice now reports only what auto-refresh will not do on its own, locally modified files and version-blocked revisions, since both need a human decision.

State file

cli-state.json (in $XDG_STATE_HOME/context7/) gains two things: an installs map keyed by absolute path, and a TTL'd contentManifest cache. The existing npm update-check fields are unchanged.

After ctx7 setup

Global CLI mode for Claude Code and Cursor, plus MCP mode at project scope. One record per installed artifact. Note the two Cursor and Claude rule records share a source file but differ in hash, because Cursor's copy carries the alwaysApply frontmatter.

{
  "installs": {
    "~/.claude/skills/find-docs": {
      "kind": "skill",
      "name": "find-docs",
      "agent": "claude",
      "scope": "global",
      "revision": 4,
      "hash": "3a69a6f34d4f",
      "files": ["SKILL.md"]
    },
    "~/.claude/rules/context7.md": {
      "kind": "rule",
      "name": "context7-cli.md",
      "agent": "claude",
      "scope": "global",
      "mode": "cli",
      "revision": 2,
      "hash": "1934a46b1e43"
    },
    "~/.cursor/skills/find-docs": {
      "kind": "skill",
      "name": "find-docs",
      "agent": "cursor",
      "scope": "global",
      "revision": 4,
      "hash": "3a69a6f34d4f",
      "files": ["SKILL.md"]
    },
    "~/.cursor/rules/context7.mdc": {
      "kind": "rule",
      "name": "context7-cli.md",
      "agent": "cursor",
      "scope": "global",
      "mode": "cli",
      "revision": 2,
      "hash": "5410731229e2"
    },
    "/repo/.claude/skills/context7-mcp": {
      "kind": "skill",
      "name": "context7-mcp",
      "agent": "claude",
      "scope": "project",
      "revision": 3,
      "hash": "ad156f258515",
      "files": ["SKILL.md"]
    },
    "/repo/.claude/rules/context7.md": {
      "kind": "rule",
      "name": "context7-mcp.md",
      "agent": "claude",
      "scope": "project",
      "mode": "mcp",
      "revision": 2,
      "hash": "6266214a08c5"
    }
  }
}

Manifest cache

Written on the first check and reused for 24h. Also serves as the last-known-good copy when the network is unavailable, so an offline ctx7 update reports against the cache instead of failing.

{
  "contentManifest": {
    "fetchedAt": 1753699200000,
    "manifest": {
      "schema": 1,
      "skills": {
        "find-docs": {
          "revision": 4,
          "minCliVersion": "0.0.0",
          "files": [{ "path": "SKILL.md", "hash": "5bf22997630f" }]
        },
        "context7-cli": {
          "revision": 3,
          "minCliVersion": "0.6.0",
          "files": [
            { "path": "SKILL.md", "hash": "d7b70ba126d9" },
            { "path": "references/docs.md", "hash": "144d463d5aa6" }
          ]
        }
      },
      "rules": {
        "context7-cli.md": { "revision": 2, "minCliVersion": "0.0.0", "hash": "9716d0bcec92" }
      }
    }
  },
  "contentNotifiedAt": 1753699200000
}

Fallback install

When the manifest is unreachable, setup still installs via the existing GitHub API path and records revision: 0. The next successful check sees any real revision as newer and offers the update.

{
  "installs": {
    "~/.claude/skills/find-docs": {
      "kind": "skill",
      "name": "find-docs",
      "agent": "claude",
      "scope": "global",
      "revision": 0,
      "hash": "3a69a6f34d4f",
      "files": ["SKILL.md"]
    }
  }
}

Output

Auto-refresh during an agent-style invocation. Nothing is printed; only the state file moves:

$ node -e '...bump cached manifest to r7...'
$ ctx7 docs /vercel/next.js "middleware config" | head -4
   (docs output, unchanged)

$ jq '.installs[] | {name, revision}' cli-state.json
  find-docs        r0 -> r7
  context7-cli.md  r0 -> r7

ctx7 update --check, covering all three outcomes:

  + Skill find-docs (Claude Code, global) r1 -> r2
    ~/.claude/skills/find-docs
  ~ Skill find-docs (Cursor, global) modified locally, skipping
    ~/.cursor/skills/find-docs
  ~ Skill context7-mcp (Claude Code, project) needs ctx7 >= 99.0.0

Run with --force to overwrite locally modified files.
You are on v0.5.6. Run ctx7 upgrade to unlock newer content.

ctx7 update --json for the same state:

[
  {
    "path": "~/.claude/skills/find-docs",
    "kind": "skill",
    "name": "find-docs",
    "agent": "claude",
    "scope": "global",
    "installedRevision": 1,
    "latestRevision": 2,
    "edited": false,
    "blockedBy": null
  },
  {
    "path": "~/.cursor/skills/find-docs",
    "kind": "skill",
    "name": "find-docs",
    "agent": "cursor",
    "scope": "global",
    "installedRevision": 1,
    "latestRevision": 2,
    "edited": true,
    "blockedBy": null
  },
  {
    "path": "/repo/.claude/skills/context7-mcp",
    "kind": "skill",
    "name": "context7-mcp",
    "agent": "claude",
    "scope": "project",
    "installedRevision": 1,
    "latestRevision": 3,
    "edited": false,
    "blockedBy": "99.0.0"
  }
]

Notes for review

  • Existing installs record revision: 0 until the manifest lands on master, so the first ctx7 update after this ships will report everything as outdated. That is the intended one-time migration.
  • Codex and OpenCode both write to ~/.agents/skills, so that path's install record reflects whichever agent ran last and the other will read as locally modified. Pre-existing and not addressed here, but now visible.

Skills and rules are versioned by a checked-in content manifest served from
raw.githubusercontent.com, and installs are tracked in the CLI state file, so
content changes reach existing installs without a CLI release.
@linear-code

linear-code Bot commented Jul 28, 2026

Copy link
Copy Markdown

CTX7-1879

The update notice was TTY-gated, so agent-driven runs (npx ctx7@latest docs,
always piped) never saw it and never refreshed. Out-of-date skills and rules
are now rewritten silently in the preAction hook, guarded by a stale-tolerant
lock so concurrent agent invocations cannot drop install records.

Locally modified and version-blocked items are still left alone; the
interactive notice now reports only those, since they need a human decision.
Refresh reused installSkill, which falls back to the legacy GitHub download
and records revision 0. When the manifest was reachable but its content was
not, that rewrote files and downgraded the record below what was installed,
so the same item looked outdated on every subsequent command and re-downloaded
forever.

Refresh now resolves strictly from the manifest and refuses to write anything
below the target revision; the fallback stays on the setup path where it
belongs. A one-hour backoff stops a broken mirror being retried per command.

Also: serialize state writes through a re-entrant, stale-tolerant lock with a
250ms cap so no command stalls behind another process, write state atomically
via temp-and-rename, and quarantine an unparseable state file instead of
silently disabling install tracking forever.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant