Skip to content

T-6OS7-rewire-skill-prose-gate-onto-contract

Status: closed/done · Impact: high · Complexity: medium

Point sdlc gate skill-prose at the new contract: compile and run the markdown-contract contract instead of importing lintSkill from the bespoke matcher. The op keeps its name, its --paths / --diff-only inputs, and the exact finding-output format that the lefthook recipe and check_skill_prose.ts wrapper parse — so the rewire is invisible to every consumer while the engine underneath changes.

LocationRole today
plugin/lib/services/gate/ops/skill-prose.tsThe gate op; imports lintSkill from _skill_prose_core.ts, resolves --paths / --diff-only, emits finding lines
.claude/skills/project-check/check_skill_prose.tsThe project-check wrapper that shells the gate op and parses its finding lines
.claude/skills/project-check/skill_md_harness.tsThe shared SKILL.md harness that already runs sibling checks against markdown-contract; the model for a thin contract caller
lefthook.ymlPre-commit recipe that invokes the gate and parses its output / exit code

Swap the op’s engine while preserving its entire external surface.

  • In skill-prose.ts, replace import { lintSkill } from "./_skill_prose_core.ts" with the runner from _skill_prose_contract.ts. In the handler, for each SKILL.md select the per-skill contract by slug, run contract.validate(source, { path }), and map findings → the existing Violation[] ({ path, line, message }) that renderSkillProse already prints.
  • Preserve: op path ["gate","skill-prose"], the paths / diffOnly / displayRoot inputs, the staged-diff seam, the skills walk, the <path>:<line>: <message> render, exit 1 on violations, and the summary line.
  • withInvariants count. It currently counts sibling invariants.yaml; during this phase the sidecars still exist (deleted in T-UDY1) so it keeps working, but it now means “covered by the contract.” Leave the wrapper’s partition for T-UDY1; only the op changes here.
  • Honest byte-stability. Exit codes and the <path>:<line>: <message> shape stay stable, but the message text changes (markdown-contract messages ≠ the old pyRepr-quoted strings). Because the op stops reading sidecars, the sidecar-driven check_skill_prose.test.ts (it writes an ephemeral invariants.yaml and expects enforcement) no longer applies — rewrite it here, in lockstep, onto the contract path (a real skill with a real contract violation), and confirm the lefthook project-check-skill-prose recipe still gates red on a true violation.
LocationKindChange
plugin/lib/services/gate/ops/skill-prose.tsmodifyImport the contract runner instead of lintSkill; per-skill select + validate + map findings to Violation[]; keep op path / inputs / render / exit
.claude/skills/project-check/tests/check_skill_prose.test.tsmodifyRewrite the sidecar-driven cases onto the contract path (the op no longer reads sibling invariants.yaml); assert exit-code passthrough, a <path>:<line> citation, and a real violation
  • AC-1: sdlc gate skill-prose compiles and runs the per-skill contract from T-FQFJ; the op name, --paths, and --diff-only inputs are unchanged.
  • AC-2: Exit codes and the <path>:<line>: <message> output shape are stable; where finding-message text changes, check_skill_prose.test.ts and any lefthook output assertions are updated in the same PR. The project-check-skill-prose lefthook recipe gates red on a real violation.
  • AC-3: No project-local phrase matcher remains in the gate path; the op resolves phrases through markdown-contract only.
  • Deleting _skill_prose_core.ts — the next task removes it once the gate no longer imports it.
  • Retiring the invariants.yaml sidecars or migrating fixtures — later phase-C tasks.

T-FQFJ-author-skill-prose-contract


← Back to Tasks