Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
235 changes: 235 additions & 0 deletions .claude/skills/_ctx-surface-audit/SKILL.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,235 @@
---
name: _ctx-surface-audit
description: "ctx-repo-internal (note the _ prefix; sibling of _ctx-command-audit / _ctx-audit). Out-of-band audit: scan a git ref range for ctx user-facing surfaces — new ctx subcommands, flags, behavior — that landed without matching SKILL.md, recipe, or docs/cli updates. Run from a SEPARATE Claude Code session, not the one that wrote the code. Drops a report at .context/audit/surface.md for the ctxctl audit-relay hook to relay verbatim."
allowed-tools: Bash(git:*), Bash(rg:*), Bash(grep:*), Bash(find:*), Read, Glob, Grep, Write
---

You are the **surface audit**: an out-of-band reviewer that
catches user-facing changes landing without matching agent
SKILL.md, recipe, or `docs/cli` updates.

This skill is **internal to the ctx repository** (the `_`
prefix marks it as repo-only dev tooling, like
`_ctx-command-audit` and `_ctx-audit`; it is not bundled into
end-user installs). It hard-codes ctx's own directory layout
(`internal/cli/`, `internal/assets/commands/`,
`internal/config/embed/`, `docs/recipes/`). It is the
reference *producer* for the generic audit channel
(`ctxctl audit` + `ctxctl audit-relay`), which lives in the
maintainer-only `ctxctl` binary (not the shipped `ctx`
binary); a downstream project that wants the pattern writes
its own audit skill targeting its own conventions and drops
reports into the same `.context/audit/` channel.

The whole point of this skill is **fresh-context judgment**.
The agent that just shipped a feature has tunnel vision; you
do not. You read the diff cold and ask: "if a user runs `ctx
help` or asks `/ctx-<area>` to do this new thing today, will
the help text / skill / recipe match what the code does?"

## Trust Boundary (Refuse Loudly)

Before reading anything, run `git status --porcelain` and `git
diff --stat`. If the working tree is **not clean** for the
audit target range, **refuse**:

> Run this audit from a separate Claude Code session. The
> current worktree has uncommitted changes to the range I am
> being asked to audit. The implementer cannot grade their
> own homework. Commit or stash here first, then re-invoke
> me in another session.

This is non-negotiable. The channel exists because in-band
judgment fails; running the audit inside the implementing
session defeats the design.

## Inputs

- **Target range**: defaults to `main..HEAD`. User may pass a
different ref pair as a positional argument.
- **Repository state**: assumed clean per the Trust Boundary
check.

## What to Scan

For the diff `git diff --name-status <range>`:

1. **New `ctx` subcommands**: look for new entries in
`internal/assets/commands/commands.yaml`, new files under
`internal/cli/*/cmd/<name>/`, new `Use*` and `DescKey*`
constants in `internal/config/embed/cmd/`.
2. **New flags**: new entries in
`internal/assets/commands/flags.yaml`, new `DescKey*Flag`
constants in `internal/config/embed/flag/`, new
`flagbind.*Flag` calls in subcommand `cmd.go` files.
3. **New behavior on existing commands**: changed RunE
bodies, new branches in existing flag handling, new
output strings in `internal/write/<area>/`.
4. **New skill triggers**: changes to existing
`SKILL.md` files that name new user-typed phrases (the
inverse direction — code change came first, skill row may
need to follow).
5. **New i18n keys**: new entries in
`internal/assets/commands/text/*.yaml` indicating new
user-visible strings.

## Coverage Checks per Surface

For each surface you find, check each location in order. Stop
at the first miss and record it; do not assume later
locations are correct.

### A. SKILL.md command-mapping table

For a new subcommand or flag in area `<X>`, the canonical skill
is at `internal/assets/claude/skills/ctx-<X>/SKILL.md`. Inside
it, the "Command Mapping" table (a table headed `| User intent
| Command |`) must list the new surface with at least one
natural-language trigger phrase.

- If the file exists and the row is present: PASS.
- If the file exists and the row is missing: FAIL — record
the surface, the file path, and the missing row shape.
- If the file does not exist: FAIL — note that the skill
area has no SKILL.md at all (much larger gap).

### B. Recipe coverage

For a new subcommand or flag, scan `docs/recipes/*.md` for any
recipe whose title or "Commands and Skills Used" table
mentions the parent command. If any do, that recipe must
mention the new surface (in the commands table or in a
walked-through step).

- If recipes mention the parent command and one of them now
references the new surface: PASS.
- If recipes mention the parent command but none reference
the new surface: FAIL — list the affected recipes.
- If no recipes mention the parent command and the surface
is a NEW workflow shape (e.g. a new subsystem), recommend a
new recipe under `docs/recipes/<area>-<workflow>.md`.

### C. `docs/cli/<command>.md`

If a per-command page exists at `docs/cli/<command>.md`, it
must mention the new subcommand or flag.

- Page exists and updated: PASS.
- Page exists and stale: FAIL — name the page.
- Page does not exist: not a hard fail (per-command pages
are optional in this repo), but note as INFO.

### D. Integrations parallel-skill (`copilot-cli` etc.)

If `internal/assets/integrations/copilot-cli/skills/ctx-<X>/`
exists, the same SKILL.md row must appear there too.

- Updated: PASS.
- Missing row: FAIL — record the file path.
- Directory does not exist: skip (no parallel skill).

## Report Format

Write the report to `.context/audit/surface.md`. Overwrite if
present (one report per kind; history lives in the dismissal
ledger).

Exact shape — frontmatter delimited by `---`, fields in order
listed:

```
---
kind: surface
status: <findings|clean>
commit-range: <ref-from>..<ref-to>
generated-at: <RFC3339 UTC, e.g. 2026-05-24T14:30:12Z>
generator: /ctx-surface-audit
digest: <short opaque digest of the findings body>
---
<verbatim body suitable for direct relay>
```

### Body shape — `status: findings`

```
Commit <SHA-or-range> added user-facing surface without docs:

• New subcommand `ctx <command>`
- SKILL.md: <path> command-mapping table is missing the row
- Recipe: <path> mentions `ctx <command>` but not the new subcommand

• New flag `--<flag>` on `ctx <existing-command>`
- SKILL.md: <path> Execution section omits this flag

Fix:
- edit <path-1>
- edit <path-2>
- consider adding a new recipe at docs/recipes/<suggested-slug>.md
```

Keep wording concrete. Prefer file paths over abstract names.

### Body shape — `status: clean`

```
No surface drift detected in <ref-from>..<ref-to>.

Surfaces scanned: <N>
Coverage checked: SKILL.md, recipes, docs/cli, integrations
```

A `clean` report is still useful — `ctxctl audit list` shows
it with a timestamp, so the user knows the audit ran.

### Digest

Compute a short opaque digest of the findings body (say, first
7 hex chars of SHA-256 of the body bytes). Used by the
dismissal ledger to detect "fresh findings" — a re-audit that
produces the same digest stays dismissed; new findings clear
the dismissal.

## Execution Steps

1. Run the dirty-tree guard. Refuse if non-clean.
2. Compute the target range (default `main..HEAD`).
3. Run `git diff --name-status <range>` and `git log
--oneline <range>` to set the scope.
4. Identify surfaces per the categories above.
5. For each surface, run the coverage checks in order.
6. Compose the body. Compute the digest.
7. Write `.context/audit/surface.md` with the structured
frontmatter + body.
8. Print a one-line summary to the user: report path,
surface count, finding count, and the next-step hint
("Open a working session — the audit-relay hook will
relay the findings on the next prompt.").

## Important Notes

- You write a report; you **do not** edit code, SKILL.md,
recipes, or any other surface. Remediation is the
in-session agent's job. Crossing that boundary makes you
the implementer and re-opens the tunnel-vision hole.
- The report body becomes the verbatim relay body. Anything
you put in there will be echoed at the user (and the
next agent) one-for-one. Keep it specific and actionable;
no editorial padding.
- Empty findings (`status: clean`) is a successful outcome,
not a problem. Write the report anyway so dismissal /
staleness tracking has a basis.
- The default `main..HEAD` covers the current branch. For
auditing a single commit, the caller can pass a range
like `<sha>^..<sha>`.

## See Also

- `specs/audit-channel.md`: design rationale, retention
policy, naming-collision notes.
- `internal/ctxctl/cli/audit/`: logic behind `ctxctl audit
list / show / dismiss`.
- `internal/ctxctl/cli/checkaudit/`: the `ctxctl audit-relay`
hook logic that relays your reports.
- `.context/CONVENTIONS.md` →
*User-Facing Surface Completeness*: the canonical rule
this audit enforces.
73 changes: 73 additions & 0 deletions .context/CONVENTIONS.md
Original file line number Diff line number Diff line change
Expand Up @@ -304,3 +304,76 @@ variants. Linters in `hack/` enforce the hard rules.
file / preserve existing keys / skip when registered / reject malformed JSON

- Substrate vs. artifact placement: cognitive substrate (consumed and mutated via ctx-mediated paths — `ctx agent`, `ctx decision add`, `/ctx-kb-ingest`, `/ctx-handover`, ceremonies) lives under `.context/`; project artifacts (read and edited directly by humans — `specs/`, `CLAUDE.md`, `GETTING_STARTED.md`, `docs/`) live at the project root; tool config and tool homes (`.ctxrc`, `.claude/`) live at root by dotfile/tool convention. The kb is substrate, not artifact: direct file edits remain possible per Invariant 1, but the skill-mediated path is the discipline. Rationale recorded in DECISIONS.md.

## User-Facing Surface Completeness

When a change adds or alters a user-facing surface — a new
`ctx` subcommand, a new flag, an observable behavior change,
a new exit shape, a new output line — the work is **not
complete** until every one of the following has been updated
in the same commit (or the same stacked PR, with the user's
explicit OK):

- `internal/assets/commands/commands.yaml` and
`examples.yaml` for the subcommand description and example
- `internal/assets/claude/skills/ctx-<area>/SKILL.md` so the
agent knows the surface exists and when to trigger it
- `internal/assets/integrations/copilot-cli/skills/<...>` if
a parallel skill exists for the integration
- `docs/recipes/<related-recipe>.md` for any recipe that
already demonstrates the broader feature; consider a new
recipe if the surface is its own workflow shape
- `docs/cli/<command>.md` if a per-command CLI doc page
exists for this surface

Splitting these into a "Phase 2 / follow-up commit / future
sweep" is **deferral** in the Constitution's sense, no matter
how the phase is labeled. Docs are part of the deliverable,
not a separable improvement. The "I can create a follow-up
task" prohibition applies verbatim.

Acceptable exceptions (state them in the commit body):

- The surface is internal-only (no human user encounters it).
- A recipe / skill genuinely does not exist for this feature
area and writing one is itself a larger separable piece of
work (then file the spec for that piece in the same commit,
do not just defer).

The Self-check before declaring a feature commit complete is:
*"If a user runs `ctx help` or asks `/ctx-<area>` to do this
new thing today, will the help text / skill / recipe match
what the code does?"* If no, the commit is not complete.

## Maintainer-Only Binaries (Layout and Installation)

Maintainer-only binaries — tooling that must never ship to end
users — live in `tools/<name>/` as separate Go modules. The
module path is lexically nested under the main ctx module
(`github.com/ActiveMemory/ctx/tools/<name>`) so the new module
CAN import the parent's `internal/` packages (Go's
internal-import rule is path-lexical, not module-scoped — see
LEARNINGS.md), reusing `rc`, `desc`, `nudge`, `config`
primitives without duplication.

Build and install:

- Built to `dist/<name>` via `make <name>` (keeps the repo
root clean).
- PATH-installed to `/usr/local/bin/<name>` via
`make install-<name>` / `make reinstall-<name>` —
mirroring ctx's `install` / `reinstall` targets so one
binary serves every worktree and repo copy.
- The shipped `ctx` binary's `go.mod` must NOT `require` the
maintainer module, giving a **hard module-graph guarantee**
that the maintainer code can never leak into `ctx`.

Repo-local hooks calling the maintainer binary live in the
gitignored `.claude/settings.local.json`, **not** in the
shipped `internal/assets/claude/hooks/hooks.json`. The hook
command shape is `cd "$CLAUDE_PROJECT_DIR" && <name>
<subcommand>` (PATH binary, project-root cwd so `.context/`
resolves correctly under cwd-anchoring).

`tools/ctxctl/` is the first inhabitant. Future maintainer
binaries follow the same shape.
Loading
Loading