Skip to content
cccskills
FreePublished on GitHub

claude-md-drift-check

Use when detecting drift between CLAUDE.md (or AGENTS.md, the Codex CLI alias) / _meta narrative and live repository state. Ten checks: absolute-path resolution, 01-projects/ count claims, issue-reference freshness, session-file existence, command-count sync, session-config-parity (mandatory template keys = error, opt-in gaps = note, unknown local keys = warning), vault-dir-parity (CLAUDE.md vs AGENTS.md), generated-rule-staleness (WARN-only), rule-scoping (paths:/globs: frontmatter defects, dangling rule citations, zero-match globs), and docs-parity (docs/components.md count-claims vs on-disk counts, template-vs-reference config-key parity, stale .claude/metrics/ paths). Full per-check spec in the body table. Invoked as an opt-in session-end phase; mirrors vault-sync's lean JSON+exit-code contract.

The English summary is being prepared. Showing the original description.

Kanevry/session-orchestrator52 stars(GitHub 52 + cccskills 0)

Run score Not tested

See how to install

Files included (4)

  • SKILL.md23.4 KB
  • checker.mjs88.3 KB
  • checker.sh991 B
  • package.json447 B

SKILL.md (original)

Review the instructions the agent will receive before you install.

CLAUDE.md Drift-Check Skill

The instruction file is alias-resolved per skills/_shared/instruction-file-resolution.md: CLAUDE.md (Claude Code / Cursor IDE) wins ties; AGENTS.md (Codex CLI) is picked up as a transparent alias when CLAUDE.md is absent. The resolved path and kind are surfaced in the JSON output (resolved_path, resolved_kind).

Status

PHASE 1 IMPLEMENTED (2026-04-19). Session-end opt-in quality gate. Upstream of /close commit preparation, downstream of vault-sync.

Why this exists

CLAUDE.md is narrative SSOT for a repo's Session Config and project context. It decays quickly when surrounding state changes — paths get renamed, project counts shift, issues close, session files get pruned in digests. The drift-cluster closed by agents/vault#57 (3 items in one sweep) was the 4th incidence of the issue-description-drift-stale-filecount learning. Manual curation does not scale; this skill turns drift detection into a repeatable gate.

Checks

#CheckWhat it scansHow
1path-resolverEvery absolute path /Users/… in scope filesexistsSync(path); missing paths under validated owner.yaml drift-check.foreign-host-prefixes are warnings (#937)
2project-count-syncHardcoded "N registered" / "N projects" claims next to 01-projects/compare to ls -d 01-projects/*/
3issue-reference-freshness#NN in forward-looking sections (What's Next, Backlog, Open Issues, Offene Themen, Todo, Next Steps)glab issue view NN --repo <origin>
4session-file-existence50-sessions/YYYY-MM-DD-*.md references anywhere in scopeexistsSync(vault/50-sessions/<file>)
5command-count"N commands" / "N /commands" claims in prosecompare to the name-deduplicated union of commands/*.md and skills whose user-invocable is true or absent (Claude Code picker semantics, #1515); skipped only when both directories are absent
6session-config-parityTop-level keys under ## Session Config in CLAUDE.md / AGENTS.mddiff against docs/session-config-template.md; a missing MANDATORY (minimal-baseline) key is an error, a missing OPT-IN-baseline-only key is a note, a local key absent from the template's Session Config blocks is a warning
7vault-dir-parityvault-integration.vault-dir in BOTH CLAUDE.md AND AGENTS.mdreuse _parseVaultIntegration; flag when the two files disagree. Short-circuits to PASS when the two are the SAME file by construction (symlink / same inode / byte-identical generated copy)
8generated-rule-staleness (WARN only).claude/rules/*.md with auto-generated: true frontmatterextract learning-key; WARN when the key is absent from .orchestrator/metrics/learnings.jsonl or its learning's expires_at is in the past; skipped silently when no auto-generated rules exist
9rule-scoping.claude/rules/*.md frontmatter + ## See Also footers + .claude/rules/<name>.md citations in CLAUDE.md/AGENTS.mdseven probes: paths: frontmatter incl. frontmatter not at line 1 (error, #1449), cited-but-missing rule citations (error), zero-match globs: patterns (warn), foreign PascalCase glob tokens (warn), unreadable rule files (warn — surfaced instead of silently skipped), declared fleet-intent globs (note, never warn), globs: without paths: (warn, #1638 — Claude Code loads it always-on); skipped silently when .claude/rules/ is absent
10docs-paritydocs/components.md count-claims, Session Config key parity (docs/session-config-template.md vs docs/session-config-reference.md), .claude/metrics/ stale-path references in docs/*.md / docs/examples/*.mdthree sub-checks (a/b/c) — see below; skipped silently when docs/components.md is absent

Check 3 deliberately scopes to forward-looking sections. Mentions inside "Recently Closed", "Decisions", "Archive", etc. describe history and must not be flagged.

Check 5 uses countSlashCommands() to count the union of non-hidden *.md files directly inside commands/ and skills under skills/ whose frontmatter explicitly declares user-invocable: true, deduplicated by name. The commands/ directory is resolved relative to VAULT_DIR by default; --commands-dir <path> overrides that side of the union, while skills remain relative to VAULT_DIR. Either directory alone is sufficient; an existing directory with no commands contributes zero rather than causing a skip.

Check 6 (issue #30; severity split per #785 and #1356) extracts the YAML block under ## Session Config from both the canonical template (docs/session-config-template.md by default, override with --config-template) and the resolved local instruction file — reading the template TWICE, once per occurrence: { occurrence: 'first' } reaches the "Starter config" at the top of the template (the 7 schema-mandatory keys), { occurrence: 'last' } reaches the "Full opt-in baseline" (the full key catalog, a strict superset of the minimal block). A template key missing from the local file is a session-config-parity error when it belongs to the minimal-baseline set, and a note with probe: "opt-in-gap" when it is opt-in-baseline-only. A local key absent from those template blocks produces a warning: it may be a typo or a template gap, and this check makes no claim about documentation elsewhere or runtime use. When the template carries a single ## Session Config heading (no separate opt-in block), 'first' and 'last' resolve to the same block, every template key is treated as mandatory, and unknown-local-key warnings are skipped. Both fenced YAML (```yaml ... ```) and raw YAML body (up to next ## heading) are accepted. The check skips gracefully when the template file is absent, when no instruction file is detected, or when explicitly disabled via --skip-session-config-parity.

The parity set is template-driven: every column-0 YAML key under ## Session Config in the template (across both occurrences) is checked. As of the gsd Pattern Adoption Quick-Wins bundle (PRD 2026-05-22, issues #517–#521), the template-side keys include four opt-in top-level blocks:

  • state-md-lock (Pattern 1 / #518) — mechanical STATE.md write lock
  • slopcheck (Pattern 2 / #520) — opt-in package legitimacy gate
  • templates-first (Pattern 3 / #519) — gh/glab template-read enforcement hook
  • verification-auto-fix (Pattern 4 / #521) — opt-in auto-fix retry loop after Quality-Gate fail

A local CLAUDE.md / AGENTS.md that omits any of these surfaces a session-config-parity note — they are opt-in-baseline-only keys, so omitting them never trips mode: hard, and never blocks autonomous-gated skill-evolution's runConfigValidationGate() (which runs the checker with --mode hard and reads only errors[]).

Check 7 (issue #600) is the only check that intentionally reads BOTH instruction files rather than the single alias-resolved one. The alias rule (CLAUDE.md wins ties, AGENTS.md is the Codex alias) means resolveInstructionFile() picks exactly one — so a repo carrying both files can silently let AGENTS.md drift out of sync with CLAUDE.md. A sibling project ran for weeks with a correct vault-integration.vault-dir in CLAUDE.md and a dead path in AGENTS.md. Check 7 reads vault-integration.vault-dir from each file (reusing the _parseVaultIntegration parser from scripts/lib/config/vault-integration.mjs — no hand-rolled YAML) and flags a vault-dir-parity error when the two values diverge (the error is attributed to AGENTS.md, the secondary alias, and names both values). The check skips gracefully when only one instruction file is present (nothing to compare), when neither file declares a vault-integration: block, or when explicitly disabled via --skip-vault-dir-parity. Two files that both omit vault-dir (both unset) agree and pass.

Runtime override caveat. SO_CONFIG_FILE=AGENTS.md reads a config-free @CLAUDE.md pointer as raw text; parse-config.mjs does not follow its import. Check 7 accepting that pointer does not prove an effective runtime config. Set the override to the config-bearing file (for example, SO_CONFIG_FILE=CLAUDE.md).

Alias-by-construction short-circuit. Since this repo's root AGENTS.md became a GENERATED byte-identical copy of CLAUDE.md (scripts/generate-agents-skills.mjs), Check 7 no longer skips here — it RUNS on every invocation. Two files that cannot disagree have nothing to diff, so the check reports parity as satisfied without deriving one: instructionFilesAreAliased() recognises a symlink resolving to the same path, the same inode/device (hardlink or already-resolved symlink), and byte-identical content. Branch order is load-bearing: the "neither file has a vault-integration: block" skip is evaluated FIRST, because an absent subject is a different (and more informative) outcome than "the files cannot disagree". The error class the check exists for is untouched — two INDEPENDENT files that declare different vault-dir values still produce the vault-dir-parity error attributed to AGENTS.md.

The same predicate guards Check 9's probe 2a, which iterates ['CLAUDE.md', 'AGENTS.md']: with an aliased AGENTS.md it scans CLAUDE.md only, because otherwise every dangling .claude/rules/*.md citation would be reported TWICE — one defect, two errors, and in --mode strict a doubled blocking surface against a file the operator is not allowed to edit.

Check 9 (rule-scoping) validates .claude/rules/*.md frontmatter against the scripts/lib/rule-loader.mjs contract, catching the class of defect where a rule silently drifts out of the activation pipeline the loader actually implements. Seven probes: (1) paths-presence — a frontmatter block that does not open on line 1 (text or a provenance comment before ---) is an ERROR since #1449: Claude Code ignores it and loads the rule always-on; the probe reports parseGlobsFrontmatter(...).defect === 'frontmatter-not-at-top' with the remedy (move the block to line 1, provenance comment after the closing ---). Otherwise, since #795 paths: is a full same-shape alias for globs: in rule-loader.mjs (globs: wins silently when both are present), so a well-formed paths:-only rule loads correctly SCOPED and is NOT flagged (#840). The probe fires only when the frontmatter textually carries a top-level paths: key AND parseGlobsFrontmatter — the loader's own parser, imported rather than re-derived — returns globs === null (e.g. a malformed frontmatter line makes it throw); that parse mismatch means the rule may silently load ALWAYS-ON, and is flagged as an error. (2) cited-but-missing — (a) .claude/rules/<name>.md citations inside CLAUDE.md/AGENTS.md that don't resolve to a file on disk, and (b) bare <name>.md tokens in a rule's own ## See Also footer that don't exist as sibling rule files (tokens carrying a path separator, e.g. ../../skills/_shared/state-ownership.md, are cross-directory references and explicitly out of scope); both flagged as errors. (3) zero-match-globs — a globs: pattern matching zero files in git ls-files (falls back to a manual directory walk when git is unavailable); flagged as a WARNING, not an error, because library/exemplar repos legitimately carry dead stack rules. (4) foreign-glob — a glob pattern containing a PascalCase product-like token (regex [A-Z][a-z]+[A-Z], e.g. WalkAITalkieTests) — a likely copy-paste leftover from another project's rule scope; flagged as a WARNING. (5) unreadable-file — a rule file that could not be read (permissions, a race with a concurrent delete); flagged as a WARNING rather than silently skipped, so a completeness audit does not drop files, and so an unreadable file cannot brick the gate under mode: hard. (6) fleet-intent-glob — a globs: pattern DECLARED as fleet intent goes to notes[] (see below), never to warnings[], and the declaration also settles probe 4 for that same pattern: a pattern the rule author declared is by construction not a copy-paste leftover. (7) globs-without-paths (#1638) — a line-1 frontmatter with a globs: key and no paths: key: Claude Code reads only paths: and ignores globs: without an error, so the rule loads ALWAYS-ON there although rule-loader.mjs scopes it for wave injection. Flagged as a WARNING with probe: "globs-without-paths", the line of the globs: key and the remedy (add paths: with the same patterns); never an error, also not under --mode strict. Keyed on the parser's raw paths field, so body text and a displaced block (probe 1) never trigger it. Glob matching reuses the same picomatch-with-inline-fallback resolution scripts/lib/rule-loader.mjs uses (parseGlobsFrontmatter is imported directly; the picomatch resolution itself is duplicated locally since rule-loader.mjs does not export a public matcher function). The check is skipped silently (no checks_run entry, no checks_skipped entry) when .claude/rules/ is absent, or explicitly via --skip-rule-scoping.

Fleet-intent declaration (Check 9, probe 6). A rule may declare that a globs: pattern is aimed at CONSUMER repos rather than this one, via its own frontmatter key fleet-intent-globs:. Accepted forms are block style (fleet-intent-globs: plus indented - "<pattern>" lines), flow style (fleet-intent-globs: ["a", "b"]) and a single inline value; surrounding quotes are stripped, blank and # comment lines are ignored. The key is inert for rule loading — rule-loader.mjs's parseGlobsFrontmatter skips unknown top-level keys and their continuation lines. A declared pattern is reported in notes[] with its file, the pattern, whether it matches ≥1 or 0 tracked files here, and the declaration source (frontmatter). The grain is per pattern AND per rule file: declaring one glob amnesties neither a sibling glob in the same rule nor the same pattern in another rule. There is deliberately NO checker-side exemption list: a rule that does not declare the pattern itself is not exempt, so the declaration always sits with the rule it describes.

Check 10 (docs-parity, issue #780) validates the public docs surface against live repository state via three sub-checks, all reported under the single docs-parity check id: (a) count-claims — docs/components.md's own heading counts ("## Skills (N user-facing)", "## Commands (N)", "## Agents (N typed sub-agents)", "## Hook event types (N)") are compared against the SAME actual on-disk derivation the surface-count family (Check 5) uses (countSkills/countSlashCommands/countAgents/readHookCounts) — NOT Check 5's claimRe regexes, which are tuned for CLAUDE.md/README prose phrasing and verifiably do not match components.md's own heading convention; a dedicated set of regexes tailored to the doc's actual authored structure is used instead. (b) config-block-parity — top-level Session Config keys documented in docs/session-config-template.md (opt-in baseline, via the Check 6 'last'-occurrence extractor) are compared against docs/session-config-reference.md; a key is considered documented in the reference when it appears in a yaml fence (any indent), a ##/###/#### heading, or the first cell of a markdown table row (| `key-name` | ...) — the reference's dominant documentation shape is one table row per field, so a naive fence-only extraction under-counts what it actually documents. Missing keys surface as docs-parity errors attributed to docs/session-config-reference.md. (c) metrics-path-liveness — any .claude/metrics/ path reference (stale; canonical is .orchestrator/metrics/) found in root docs/*.md or docs/examples/*.md surfaces as an error at its exact file/line. The whole check is skipped silently (no checks_run entry, no checks_skipped entry) when docs/components.md is absent, or explicitly via --skip-docs-parity.

Files

  • checker.mjs — pure Node ESM, no runtime deps. Reads scope files, runs enabled checks, emits JSON on stdout.
  • checker.sh — POSIX shim. Resolves VAULT_DIR, execs Node. No pnpm install needed (zero deps).
  • package.json — declares Node engine; no dependencies.
  • tests/ — vitest suite added in Quality wave.

Invocation

VAULT_DIR=/path/to/vault bash checker.sh --mode warn

CLI flags (all optional):

FlagDefaultEffect
--mode <hard|warn|off>warnhard → exit 1 on errors; warn → exit 0, errors in JSON; off → short-circuit to status: skipped-mode-off
--repo <owner/name>derived from git remote get-url originOverride for Check 3's glab issue view --repo
--include-path <glob>resolved instruction file (CLAUDE.md or AGENTS.md per alias rule), _meta/**/*.mdRepeatable. Scope files, relative to VAULT_DIR. Defaults are seeded post-resolution so Codex-only repos (AGENTS.md) are scanned out of the box.
--skip-path-resolveroffDisable Check 1
--skip-project-countoffDisable Check 2
--skip-issue-refsoffDisable Check 3 (also auto-skipped if glab not on PATH)
--skip-session-filesoffDisable Check 4
--skip-command-countoffDisable Check 5
--skip-session-config-parityoffDisable Check 6
--skip-vault-dir-parityoffDisable Check 7
--skip-generated-rule-stalenessoffDisable Check 8
--skip-rule-scopingoffDisable Check 9
--skip-docs-parityoffDisable Check 10
--commands-dir <path><VAULT_DIR>/commandsOverride path to commands/ directory for Check 5
--config-template <path><VAULT_DIR>/docs/session-config-template.mdOverride path to the canonical Session Config template for Check 6

Environment:

  • VAULT_DIR — project root to scan. Defaults to $PWD. Can also be passed as positional arg 1.

JSON output

{
  "status": "ok|invalid|skipped|skipped-mode-off",
  "mode": "hard|warn|off",
  "vault_dir": "<absolute path>",
  "resolved_path": "<absolute path to CLAUDE.md or AGENTS.md, or null>",
  "resolved_kind": "claude|agents|null",
  "files_scanned": N,
  "checks_run": ["path-resolver", "project-count-sync", "issue-reference-freshness", "session-file-existence", "command-count", "session-config-parity", "vault-dir-parity", "generated-rule-staleness", "rule-scoping", "docs-parity"],
  "checks_skipped": ["<name>: <reason>"],
  "errors": [
    { "check": "<name>", "file": "<relative path>", "line": N, "message": "<human>", "extracted": "<raw text>" }
  ],
  "warnings": [
    { "check": "<name>", "file": "<relative path>", "line": N, "message": "<human>", "extracted": "<raw text>" }
  ],
  "notes": [
    { "check": "rule-scoping", "probe": "fleet-intent-glob", "file": "<relative path>", "line": N, "message": "<human>", "extracted": "<pattern>", "source": "frontmatter" }
  ],
  "command_count": { "actual": N }
}

notes[] is the THIRD output category, alongside errors[] and warnings[]: a finding that is reported but asks for no action. It never affects status or the exit code, and it is always present (empty array when nothing was noted, including on the short-circuit "no scope files matched" path). Producers are Check 6's opt-in-gap probe and Check 9's fleet-intent-glob probe. Opt-in-gap notes carry check: "session-config-parity", probe: "opt-in-gap", the instruction file and line, a message, and the omitted key in extracted; they have no source field.

The resolved_path / resolved_kind pair surfaces the alias resolution outcome (issue #33 AC2) so users on either platform can audit which instruction file the checker scanned. kind: 'claude' for CLAUDE.md, kind: 'agents' for AGENTS.md, null when neither was found.

When command-count fires a drift error, the error object also carries "command_count": { "actual": N, "claimed": M } for easy programmatic diffing.

Exit codes:

  • 0 — no errors, or errors present but mode=warn, or short-circuit (mode=off / no scope files)
  • 1 — errors present and mode=hard
  • 2 — invocation or infra error (missing VAULT_DIR, unreadable file, malformed glob)

Session Config block (opt-in)

In repo-level CLAUDE.md under ## Session Config:

drift-check:
  enabled: true
  mode: warn          # hard | warn | off
  include-paths:
    - CLAUDE.md
    - AGENTS.md
    - _meta/**/*.md
  check-path-resolver: true
  check-project-count-sync: true
  check-issue-reference-freshness: true
  check-session-file-existence: true
  check-command-count: true
  check-session-config-parity: true
  check-vault-dir-parity: true
  check-generated-rule-staleness: true
  check-rule-scoping: true
  check-docs-parity: true

When drift-check.enabled is false or the block is absent, the session-end phase is a no-op.

Invocation points

Session-End Phase 2.2 — opt-in quality gate

  • Trigger: after Phase 2.1 vault-sync, before commit prep
  • Behavior: full scan of the configured include-paths
  • Error handling: mode=hard exits non-zero and session-end converts errors into carryover + continue; mode=warn surfaces in quality-gate report; mode=off skipped silently
  • Rationale: drift is narrative-level; vault-sync catches frontmatter-level. The two gates are complementary.

Future: wave-executor (not implemented)

A lightweight variant could run after Impl-Polish when CLAUDE.md is edited mid-session. Out of scope for Phase 1.

Design notes

  • No zod. Output is emission-only, input is plain text. Pure stdlib keeps dep footprint zero.
  • No frontmatter parsing. Scope files are scanned as Markdown prose; vault-sync owns frontmatter validation.
  • Code-fence aware. Path extraction skips triple-backtick blocks to avoid flagging example paths. Issue-ref extraction does NOT skip fences (configs and snippets often cite real live issues).
  • Section-aware Check 3. A #NN mention in "Recently Closed" is context; the same mention in "What's Next" is drift. The checker tracks the current ## heading to decide.
  • glab optional. If glab is missing or not authenticated, Check 3 degrades to checks_skipped with a clear reason — never blocks.

Relationship to vault-sync

Aspectvault-syncdrift-check
TargetFrontmatter + wiki-links in vault/*.mdNarrative drift in CLAUDE.md / _meta/*.md
Schema sourceVendored Zod from baselineNone — regex + filesystem checks
Depszod, yamlNone (stdlib only)
Session-end phase2.12.2
Default modewarnwarn

They are siblings, not overlapping. vault-sync is the structural gate; drift-check is the narrative gate.

Reviews

No reviews yet. Tell others what you thought.

Comments

No login needed. Share how it worked for you, ask a question, or note where it did not run.

No comments yet. Be the first to write one.

Comments are published right away. Do not include personal information such as email addresses or phone numbers. Up to 2 URLs.

0 / 1,000 characters

This form is protected by reCAPTCHA, and the Google Privacy Policy and Terms of Service apply.

More skills from this repository

Use when the user asks to improve architecture, find refactoring opportunities, surface deepening opportunities, consolidate tightly-coupled modules, or make a codebase more testable and AI-navigable. Surfaces shallow modules and hypothetical seams using a precise vocabulary (Module / Interface / Implementation / Depth / Seam / Adapter / Leverage / Locality from LANGUAGE.md).

The English summary is being prepared. Showing the original description.

Kanevry/session-orchestrator52Updated Oct 26, 2026

Use this skill when running an autonomous session-orchestration loop. Chains session-start → session-plan → wave-executor → session-end for N iterations with all 10 kill-switches (SPIRAL, FAILED wave, carryover > 50%, max-hours, max-sessions, resource-overload, token-budget, stall-timeout, sub-threshold confidence, user-abort). Reads Mode-Selector output (Phase B) to decide auto-execute vs. fallback. Writes one autopilot.jsonl record per loop run. Phase C scaffold (issue #277); implementation lives in scripts/lib/autopilot.mjs (Phase C-1 follow-up).

The English summary is being prepared. Showing the original description.

Kanevry/session-orchestrator52Updated Oct 26, 2026

Use this skill when scaffolding the minimum repository structure required by session-orchestrator. Invoked automatically by the Bootstrap Gate when CLAUDE.md, Session Config, or bootstrap.lock is missing. Also available as /bootstrap for manual invocation. Three intensity tiers: fast (demos/spikes), standard (MVPs), deep (production/team).

The English summary is being prepared. Showing the original description.

Kanevry/session-orchestrator52Updated Oct 26, 2026

Use when you have a feature idea but the scope or UX is still ambiguous — runs a lightweight Socratic design dialogue (3-5 AUQ rounds) and writes a spec markdown file. Use BEFORE /plan feature when product intent needs validation; skip to /plan feature when scope is already clear. HARD-GATE prevents any code work until the design is user-approved.

The English summary is being prepared. Showing the original description.

Kanevry/session-orchestrator52Updated Oct 26, 2026

close

Free

End session with verification, commits, and documentation

The English summary is being prepared. Showing the original description.

Kanevry/session-orchestrator52Updated Oct 26, 2026

Monitor iterative improvement loops for convergence. Three signals — shrinking diff, pass-rate plateau, velocity — drive a Stop/Continue/Investigate decision at each inter-wave checkpoint. Distinct from /evolve (retrospective) and session-reviewer (wave output review): convergence-monitoring answers "are we making progress?" not "was the last wave correct?". Primary consumer: /autoresearch loops and wave-executor inter-wave checkpoints.

The English summary is being prepared. Showing the original description.

Kanevry/session-orchestrator52Updated Oct 26, 2026

See all skills by Kanevry

Report a problem with this skill