Files
db6db34859 add od lint — CLI half of the artifact lint endpoint (#6959)
* feat(cli): od lint — CLI half of the live artifact linter

POST /api/artifacts/lint has been reachable by the web layer since the
linter landed, but had no SUBCOMMAND_MAP entry, leaving it invisible to
external agents in violation of the dual-track rule. Adds the shared DTO
in contracts and a runLint mirroring runExport's flag shape (stdin via
'-', --json envelope, --fail-on threshold, exit 1 on findings at/above).

* address review: contracts as producer authority, cli-lint entrypoint spec, root-help discoverability

- lint-artifact.ts now imports ArtifactLintFinding/ArtifactLintSeverity
  from @open-design/contracts and aliases the legacy exported names to
  them, so lintArtifact() is typed by the shared contract and a producer
  change that breaks the wire shape fails compilation instead of
  drifting behind the CLI's consumer-side assertion. The lint route
  types its request/response against LintArtifactRequest/Response.
- New tests/cli-lint.test.ts on the cli-templates/cli-deploy stub-server
  harness: file + stdin input, all four --fail-on thresholds, clean and
  finding responses, human/--json/--agent-message output, usage errors
  (unknown flag, bad threshold, missing/empty file), HTTP failure
  (structured exit 64), unreachable daemon (exit 3), and root-help
  presence. 12 specs.
- printRootHelp() lists od lint beside od export with its headless/
  threshold purpose.

---------

Co-authored-by: Cruz Morales <273886418+DojoGenesis@users.noreply.github.com>
2026-08-17 08:15:52 +00:00

54 lines
1.6 KiB
TypeScript

// Artifact lint — the anti-slop linter's wire contract.
//
// The daemon has served POST /api/artifacts/lint (and returned the same
// findings inline from POST /api/artifacts/save) since the linter landed,
// but the shape only ever existed as a private type in
// apps/daemon/src/lint-artifact.ts. This file makes it a shared DTO so the
// CLI (`od lint`) and web surfaces parse one contract instead of
// re-declaring the daemon's internals.
export type ArtifactLintSeverity = 'P0' | 'P1' | 'P2';
export interface ArtifactLintFinding {
severity: ArtifactLintSeverity;
/** Stable rule id, e.g. `purple-gradient`, `invented-metric`. */
id: string;
message: string;
/** Actionable remediation, phrased for the generating agent. */
fix: string;
/** Offending excerpt when the rule can isolate one. */
snippet?: string;
}
export interface LintArtifactRequest {
html: string;
}
export interface LintArtifactResponse {
findings: ArtifactLintFinding[];
/**
* Findings pre-rendered as an `<artifact-lint>` block, formatted to be
* spliced into the generating agent's next turn for self-correction.
*/
agentMessage: string;
}
/** Severity threshold for `od lint`'s exit code. `none` never fails. */
export type LintFailOn = 'p0' | 'p1' | 'p2' | 'none';
export interface LintArtifactCliCounts {
p0: number;
p1: number;
p2: number;
}
/** Machine envelope printed by `od lint --json`. */
export interface LintArtifactCliResultEnvelope {
ok: boolean;
file: string;
failOn: LintFailOn;
counts: LintArtifactCliCounts;
findings: ArtifactLintFinding[];
agentMessage: string;
}