/** * Canon-Lint — agent-accountability harness for raw markdown writes against * canonical doc types. * * Reads a Claude Code-style session transcript (`*.jsonl` under * `~/.claude/projects/.../*.jsonl` or `.cleo/sessions/*.jsonl`), walks every * recorded `Write` / `Edit` / `MultiEdit` tool call, and flags any * `file_path` argument whose target lands under a `rawMdPaths` entry whose * owning DocKind has `rawMdAllowed: false` in `.cleo/canon.yml`. * * This is the deferred-detection complement to the PR-time CI gate * (`cleo check canon docs`, T9796): the CI gate blocks at merge time, but * agent-accountability runs against historical transcripts so the team can * audit which agents (and which tool calls within them) bypassed SSoT. * * Design: * - Pure function. Caller supplies a transcript path + canon registry. * - Returns one violation per offending tool call (NOT per file) so the * same `file_path` overwritten three times in a session yields three * violations — useful for sequencing analysis. * - Treats missing transcript as `[]` (not an error) so callers can * batch-lint across many session ids without try/catch. * - Uses the same `CanonRegistry` shape exported by the CLI dispatch * layer's check.canon-docs module — that's the single source of * routing truth. * * @epic T9787 — SG-DOCS-CANON-CLOSURE * @task T9797 — E-DOCS-REAL-WORLD-VALIDATION * @see ADR-076 — Canonical Docs SSoT * @see packages/cleo/src/dispatch/domains/check/canon-docs.ts — CI gate sibling */ /** * One DocKind entry from `.cleo/canon.yml` — duplicated here (rather than * imported from `packages/cleo/`) because the SDK MUST NOT depend on the * CLI dispatch layer (Package-Boundary Check, AGENTS.md). The shape is * pinned by `.cleo/canon.schema.json`. */ export interface CanonKindEntry { /** `'ssot'` — blob-store-only; `'ssot-first'` — dual-write via cleo verb. */ readonly canonicalHome: 'ssot' | 'ssot-first'; /** Relative path to the human-reviewable mirror (e.g. `docs/adr/`). */ readonly publishMirror: string; /** When `false`, raw fs writes to listed paths are violations. */ readonly rawMdAllowed: boolean; /** Directories the lint scans. Optional when `rawMdAllowed: false`. */ readonly rawMdPaths?: ReadonlyArray; } /** Parsed shape of `.cleo/canon.yml`. */ export interface CanonRegistry { /** Schema version. Currently always `1`. */ readonly version: number; /** Map of DocKind id (e.g. `'adr'`, `'changeset'`) to its routing entry. */ readonly kinds: Readonly>; } /** * Categorical reason a tool call was flagged. * * Currently only `raw-md-canonical` exists. Future expansion (per the * task spec): `bypass-attempt` for renames into canonical paths, * `delete-bypass` for `rm` calls under SSoT mirrors, etc. */ export type CanonLintViolationKind = 'raw-md-canonical'; /** One violation flagged by the canon-lint scan. */ export interface CanonLintViolation { /** Session id this transcript belongs to. Derived from the file name. */ readonly sessionId: string; /** Anthropic `tool_use.id` (e.g. `toolu_01ABC...`). Empty when missing. */ readonly toolUseId: string; /** Tool name (`Write` | `Edit` | `MultiEdit`). */ readonly tool: string; /** Repo-relative file path the agent attempted to write. */ readonly path: string; /** Owning DocKind id (`'adr'`, `'note'`, …). */ readonly docKind: string; /** The `rawMdPaths` entry that matched (e.g. `'.cleo/adrs/'`). */ readonly matchedPath: string; /** Categorical reason. */ readonly kind: CanonLintViolationKind; /** * Short evidence snippet — first 200 chars of either the violating * `content` (Write) or `new_string` (Edit). Truncated so a transcript * with 50 violations stays under a few KB total. */ readonly evidence: string; /** Suggested fix-command text. */ readonly fix: string; } /** Aggregate result returned by `lintSessionForCanonViolations`. */ export interface CanonLintResult { /** Absolute path of the transcript that was scanned. */ readonly transcriptPath: string; /** Session id derived from the transcript filename. */ readonly sessionId: string; /** True when no violations were found. */ readonly passed: boolean; /** Number of `Write` / `Edit` / `MultiEdit` tool calls inspected. */ readonly scanned: number; /** Violations in transcript order. Empty when `passed === true`. */ readonly violations: ReadonlyArray; /** * Non-fatal warnings (e.g. transcript JSON parse failures on isolated * lines). Lint does NOT abort on a single bad line — it skips and * surfaces here so callers can spot data quality issues. */ readonly warnings: ReadonlyArray; /** * `'enforced'` when the canon registry was supplied; `'no-canon'` when * caller passed `undefined` (lint becomes a no-op success). */ readonly mode: 'enforced' | 'no-canon'; } /** * Load `.cleo/canon.yml` from `projectRoot`. Returns `undefined` when the * file is missing — the lint becomes a no-op success in that case. Throws * on malformed YAML so callers can surface a clear error envelope. * * Mirrors `loadCanonRegistry` in * `packages/cleo/src/dispatch/domains/check/canon-docs.ts` so SDK callers * (e.g. external session-audit tools) do NOT need to depend on the CLI * package. * * @param projectRoot - Absolute path to the repo root. */ export declare function loadCanonRegistry(projectRoot: string): CanonRegistry | undefined; /** Parameters accepted by {@link lintSessionForCanonViolations}. */ export interface LintSessionParams { /** Absolute path to the `.jsonl` transcript to scan. */ readonly transcriptPath: string; /** * Project root used to locate `.cleo/canon.yml`. Required because the * transcript may live anywhere (e.g. under `~/.claude/projects/`). */ readonly projectRoot: string; /** * Pre-loaded registry override — when supplied, skips the on-disk read. * Tests pass a synthetic registry so they don't need a real project. */ readonly registry?: CanonRegistry; } /** * Lint a single session transcript for raw-markdown writes that bypass * the docs SSoT. * * Walks the JSONL line-by-line. For each `tool_use` whose `name` is * `Write` / `Edit` / `MultiEdit`, normalises the `file_path` to forward * slashes and checks it against every `rawMdPaths` entry whose owning * kind has `rawMdAllowed: false`. Matches yield one * {@link CanonLintViolation} each (per-call resolution). * * The lint is forgiving: a malformed line surfaces as a warning, not a * fatal error, so a partial transcript still yields actionable output. * * @param params - Transcript path, project root, optional registry override. * @returns Structured result with the violation list and any warnings. */ export declare function lintSessionForCanonViolations(params: LintSessionParams): CanonLintResult; //# sourceMappingURL=canon-lint.d.ts.map