Files
Michał Pierzchała 1a76344685 docs: restructure AGENTS.md and CONTEXT.md for progressive disclosure (#1402)
* docs: restructure AGENTS.md and CONTEXT.md for progressive disclosure

Apply the Claude 5 context-engineering guidance to the repo's agent docs:
keep the always-loaded file to gotchas and invariants, and move situational
guidance one hop away behind a routing table.

AGENTS.md 315 -> 229 lines. Cut generic agent-behavior boilerplate, three-way
duplication (Common Mistakes restated Hard Rules; Finding Source Owners
restated the registry section), and facts visible from the repo itself.
Kept verbatim: the expensive-lessons principles, enforcement gates, Hard
Rules, and environment traps.

Split out docs/agents/{cli-flags,pull-requests,device-verification}.md and
folded the Testing Matrix into docs/agents/testing.md, reframed around
pnpm check:affected so the prose stops duplicating the selector.

CONTEXT.md keeps all 50 terms, now grouped under a section index so a task
loads one section instead of the whole glossary.

* fix(check-affected): move the selector-owning sentinel to the Testing Matrix

The Testing Matrix moved from AGENTS.md to docs/agents/testing.md, but the
affected-check selector still treated only AGENTS.md as selector-owning. A
later matrix edit would have been classified as inert docs and skipped the
fail-open, so the selector could keep deriving gates from a spec that had
changed underneath it.

Move the sentinel with the prose, as a named SELECTOR_OWNING_DOCS set so the
next move is one line, and fix the two in-code comments plus the testing.md
paragraph that still pointed at the AGENTS.md matrix.

* docs: restore two rules dropped by the AGENTS.md split

Review caught two repo-specific rules that did not survive the move. Both are
prose without any backticked identifier, so the identifier-diff used to verify
the split could not see them.

- "Test through public interfaces; do not add unrelated production exports
  solely to enable tests" returns next to the behavioral-tests rule in
  docs/agents/testing.md, with the reason it exists.
- The guidance-ownership rule (decide whether new guidance/schema/metadata
  belongs to the command surface, CLI grammar, CLI help, MCP projection, or
  daemon runtime) returns to the always-loaded Docs & skills section, since it
  governs all command-surface work and not just the flag case.

Also point the ADR routing row at docs/adr/README.md, which is already the
"read when you touch…" index, rather than at the bare directory.
2026-07-25 12:13:09 +02:00

199 lines
6.2 KiB
TypeScript

// Catalog for the check-affected selector: how each derived CheckId maps to a
// runnable command and the authoritative GitHub CI job(s) it mirrors.
//
// Commands are resolved from real package.json scripts or Vitest's native
// affected-test command, so this stays a thin projection over existing
// aggregate checks rather than a second source of truth for how to run them.
import { ALL_CHECKS, type CheckId } from './model.ts';
export type CheckKind =
| { readonly type: 'script'; readonly script: string }
| { readonly type: 'vitest-related' };
export type CheckSpec = {
readonly id: CheckId;
readonly label: string;
readonly kind: CheckKind;
readonly ciJobs: readonly string[];
// Whether `--run` should attempt the check locally. Device/emulator lanes and
// network/toolchain-gated lanes stay authoritative on GitHub CI.
readonly localRunnable: boolean;
};
export const CHECK_CATALOG: readonly CheckSpec[] = [
{
id: 'format',
label: 'Formatting (oxfmt)',
kind: { type: 'script', script: 'format:check' },
ciJobs: ['Lint & Format'],
localRunnable: true,
},
{
id: 'lint',
label: 'Lint (oxlint)',
kind: { type: 'script', script: 'lint' },
ciJobs: ['Lint & Format'],
localRunnable: true,
},
{
id: 'typecheck',
label: 'Typecheck (tsc)',
kind: { type: 'script', script: 'typecheck' },
ciJobs: ['Typecheck'],
localRunnable: true,
},
{
id: 'layering',
label: 'Import-direction layering guard',
kind: { type: 'script', script: 'check:layering' },
ciJobs: ['Layering Guard'],
localRunnable: true,
},
{
id: 'fallow',
label: 'Fallow code-quality audit',
kind: { type: 'script', script: 'check:fallow' },
ciJobs: ['Fallow Code Quality'],
localRunnable: true,
},
{
id: 'mcp-metadata',
label: 'MCP registry metadata sync',
kind: { type: 'script', script: 'check:mcp-metadata' },
ciJobs: ['Typecheck'],
localRunnable: true,
},
{
id: 'build',
label: 'Build (tsdown + declarations)',
kind: { type: 'script', script: 'build' },
ciJobs: ['Packaged CLI Node 22.12'],
localRunnable: true,
},
{
id: 'vitest-related',
label: 'Tests related by Vitest module graph',
kind: { type: 'vitest-related' },
ciJobs: ['Coverage'],
localRunnable: true,
},
{
id: 'unit',
label: 'Unit + smoke suite',
kind: { type: 'script', script: 'check:unit' },
ciJobs: ['Coverage', 'Integration Tests'],
localRunnable: true,
},
{
id: 'coverage',
label: 'Coverage + provider integration suite',
kind: { type: 'script', script: 'test:coverage' },
ciJobs: ['Coverage'],
localRunnable: true,
},
{
id: 'provider-integration',
label: 'Provider-backed integration suite',
kind: { type: 'script', script: 'test:integration:provider' },
ciJobs: ['Integration Tests', 'Coverage'],
localRunnable: true,
},
{
id: 'integration-node',
label: 'Node integration smoke',
kind: { type: 'script', script: 'test:integration:node' },
ciJobs: ['Integration Tests'],
localRunnable: true,
},
{
id: 'integration-progress',
label: 'Integration architecture-progress gate',
kind: { type: 'script', script: 'test:integration:progress:check' },
ciJobs: ['Integration Tests'],
localRunnable: true,
},
{
id: 'swift-runner',
label: 'Swift runner build',
kind: { type: 'script', script: 'build:xcuitest' },
ciJobs: ['Swift Runner Unit Compile', 'iOS / Smoke Tests', 'macOS / Smoke Tests'],
localRunnable: false,
},
{
id: 'android-helpers',
label: 'Android helper builds',
kind: { type: 'script', script: 'build:android-snapshot-helper' },
ciJobs: ['Android / Smoke Tests'],
localRunnable: false,
},
{
id: 'macos-helper',
label: 'macOS helper build',
kind: { type: 'script', script: 'build:macos-helper' },
ciJobs: ['macOS / Smoke Tests'],
localRunnable: false,
},
{
id: 'web-smoke',
label: 'Live web platform smoke',
kind: { type: 'script', script: 'test:smoke:web' },
ciJobs: ['Web Platform Smoke'],
localRunnable: false,
},
{
id: 'skillgym',
label: 'SkillGym command-planning suite',
kind: { type: 'script', script: 'test:skillgym' },
// No GitHub workflow runs SkillGym; per the Testing Matrix in
// docs/agents/testing.md it is a local-only gate (`pnpm test:skillgym`).
// Keep it locally runnable rather than claiming a CI job that does not
// exist and silently skipping it.
ciJobs: [],
localRunnable: true,
},
];
export function getCheckSpec(id: CheckId): CheckSpec {
const spec = CHECK_CATALOG.find((entry) => entry.id === id);
if (!spec) throw new Error(`No catalog entry for check "${id}".`);
return spec;
}
// Resolve the runnable command for a check. Script-backed checks are validated
// against package.json so a renamed/removed script fails loudly instead of
// silently skipping a gate. `fallow` threads the same --base the audit uses.
export function resolveCommand(
spec: CheckSpec,
scripts: Readonly<Record<string, string>>,
base: string,
changedFiles: readonly string[] = [],
): string[] {
if (spec.kind.type === 'vitest-related') {
return ['pnpm', 'exec', 'vitest', 'related', '--run', '--passWithNoTests', ...changedFiles];
}
const { script } = spec.kind;
if (!(script in scripts)) {
throw new Error(
`Check "${spec.id}" references package.json script "${script}", which does not exist.`,
);
}
const command = ['pnpm', 'run', script];
if (spec.id === 'fallow') command.push('--base', base);
return command;
}
// Guard: the catalog must cover exactly the CheckId universe. The self-test
// asserts this so a new check cannot ship half-wired.
export function assertCatalogComplete(): void {
const catalogIds = new Set(CHECK_CATALOG.map((entry) => entry.id));
const missing = ALL_CHECKS.filter((id) => !catalogIds.has(id));
const extra = CHECK_CATALOG.filter((entry) => !ALL_CHECKS.includes(entry.id)).map((e) => e.id);
if (missing.length > 0 || extra.length > 0) {
throw new Error(
`Check catalog out of sync with ALL_CHECKS. Missing: [${missing.join(', ')}]; ` +
`extra: [${extra.join(', ')}].`,
);
}
}