Skip to main content
Glama

ml-skills

Architecture standards you can actually enforce — served as an MCP server, an HTTP API, a CLI and a library, so the same rules reach your coding agents, your website, your pipeline and your editor without being written down four times.

npx @mlmcps/ml-skills check .          # a repo, with file:line findings
npx -p @mlmcps/ml-skills ml-skills-mcp    # MCP over stdio, for agents
npx -p @mlmcps/ml-skills ml-skills-http   # HTTP API + MCP Streamable HTTP + browser UI

The idea

Most "AI coding standards" projects ship a pile of markdown and hope. The problem is that prose is unenforceable, and an agent that reads a standard is under no obligation to follow it. So this package separates two things that usually get conflated:

What it is

Who owns it

Rules

The checks. "Pick one pagination style and use it everywhere."

Everyone. Shipped here.

Decisions

The values those checks compare against. "Ours is cursor pagination with cursor/limit."

You. Overridable, and the only part that is anyone's IP.

You cannot buy the second column — a standard's value is precisely that it is your decision, consistently applied. What you can take off the shelf is the machinery: the rule catalogue, the checkers, and three transports to reach every tool you use. That is this package.

Nothing here can fail your build until you say so

Every standard ships marked proposed. A proposed standard reports at warn and cannot exit non-zero, whatever its severity map says. You ratify it in .mlskills.json, and only then can it break a pipeline.

That is deliberate. A gate that fails on day one gets disabled on day two, and a rule nobody agreed to is a preference wearing a uniform. Every report says out loud which standards were incapable of failing, because "no errors" means something very different when nothing was allowed to be one.


The eleven standards

Standard

Governs

Rules

openapi-contract

HTTP API shape: versioning, errors, pagination, headers, leaks

22

entity-relationships

SQL keys/money/time/FKs + Mongoose schema design

36

backend-patterns

Layering, secrets, logging, timeouts, input validation

25

infra-patterns

Images, probes, limits, pod security, CI pinning

21

ui-patterns

Async states, tokens, data layer, accessibility

19

security-patterns

Injection, authz, crypto, cookies, traversal, headers

22

testing-patterns

Whether a green suite means anything

14

events-messaging

Versioning, idempotency, the outbox, dead letters

10

observability

Structured logs, correlation, traces, metrics

10

resilience-patterns

Backoff, jitter, breakers, graceful shutdown

10

config-secrets

Twelve-factor config, startup validation, secret hygiene

10

199 rules. Read one in full: npx @mlmcps/ml-skills show security-patterns.

A few that earn their keep:

  • SEC021 flags findById(req.params.id) with no ownership check nearby. Broken object-level authorisation is the most exploited API flaw there is, and it looks exactly like working code.

  • OAS009 resolves $ref transitively, so a passwordHash two schemas below a response root is still reported — the shape credential leaks actually have.

  • EVT003 flags a publish inside a transaction with no outbox: the dual-write, where the transaction rolls back, the event does not, and the estate now believes something that never happened.

  • RES010 flags catch { return [] }. The dependency is down, the page renders "no results", nothing alerts, and revenue drops for a week.

  • TST001 flags a committed .only, which silently stops the rest of the suite from running while the build stays green.

Adapters — reuse what already exists

Spectral, Semgrep, hadolint, ESLint and Trivy encode far more person-years of rule development than this package ever will, and several match on a real AST rather than on text. Reimplementing them would be worse and slower. So ml-skills runs them and normalises what they say:

ml-skills adapters                      # what is installed
ml-skills check . --with semgrep        # built-in rules plus one adapter
ml-skills check . --with-all            # plus everything installed

Adapter

Standard it reports under

Covers

spectral

openapi-contract

OpenAPI/AsyncAPI rulesets

hadolint

infra-patterns

Dockerfile

eslint

backend-patterns

your project's own ESLint config — this adapter adds no rules

semgrep

security-patterns

semantic patterns, registry rulesets

trivy

security-patterns

dependency CVEs, committed secrets, IaC misconfiguration

squawk

entity-relationships

Postgres migration safety — parses SQL, reasons about lock levels

sqlfluff

entity-relationships

dialect-aware SQL linting

spotbugs

backend-patterns

Java bytecode analysis (+ Find Security Bugs) — reads target/*.xml

pmd

backend-patterns

Java source rules — reads the build's report

checkstyle

backend-patterns

Java conventions — reads the build's report

Java adapters read the report your build already produced rather than running a JVM. PMD, SpotBugs and Checkstyle are Maven and Gradle plugins, not standalone CLIs — invoking a second copy would be slower, need its own configuration, and could disagree with the one that gated the build. Their applicability is the report's existence, not our file walk: the build may have analysed sources we never saw.

ArchUnit has no adapter and needs none. It is a test library, so layering rules run in your existing suite and fail the build with no extra gate.

Findings merge into one list, sorted together, each tagged with its source:

  Dockerfile
      1  error  INF001            base image "node:latest" uses the :latest tag
      2  warn   hadolint/DL3009 via hadolint  Delete the apt-get lists after installing something
      2  warn   INF007            apt install without pinned package versions

External findings go through the same ratification gate. An installed tool cannot grant itself the authority to fail a build on a standard nobody has signed off. Severity precedence is: your config override, then the tool's own severity, then the ratification demotion.

{
  "adapters": {
    "semgrep": { "required": true, "config": "p/owasp-top-ten" },
    "hadolint": { "severity": { "DL3059": "off" } },
    "trivy": { "scanners": "vuln,secret" }
  }
}

Stack presets

ml-skills preset                 # list
ml-skills preset mern            # write .mlskills.json
ml-skills preset java-spring

Preset

Adapters

Rules deferred

mern

5

65

java-spring

9

70

A preset answers the question that matters once you accept the ecosystem is stronger than any one package: given ~3,000 rules across twenty tools, which of ours are still worth running?

Two rules govern them, both enforced by tests:

  • Never silence a rule without naming the tool that supersedes it. Every block carries a $why. An unexplained "off" is indistinguishable from someone quietly disabling an inconvenient rule, so it is not allowed to exist.

  • A preset never ratifies anything. Every skill stays proposed. Deciding a standard may break a build is a human call, and a config file taking it on your behalf is how governance gets disabled the week after it lands.

Applying a preset over an existing config keeps your values and reports which it kept — a preset is a starting point, not an overwrite.

What each preset keeps

mern defers UI, testing and most security rules to eslint-plugin-react/-hooks/-jsx-a11y (171 rules), eslint-plugin-jest/-testing-library (100) and Semgrep — then keeps the Mongoose schema rules, because as of writing nothing in the JavaScript ecosystem checks Mongo schema design. eslint-plugin-mongodb is 17 rules about query syntax; eslint-plugin-mongoose does not exist. That is the load-bearing part of the preset.

java-spring defers bug-hunting to PMD, SpotBugs and Checkstyle, and migration safety to squawk — which parses Postgres and reasons about lock levels ("requires ACCESS EXCLUSIVE; blocking: reads, writes"), an analysis no pattern over text can reach. It keeps the contract and design decisions: layering, entities leaving the API layer, timeouts, PII, transaction placement, key type, cross-service foreign keys.

Both keep events-messaging, observability and resilience-patterns in full, because nothing in either ecosystem checks the outbox pattern, correlation propagation, or backoff-with-jitter. resilience4j is a library — using it does not mean it was configured correctly.

A tool that did not run is never a clean result

This is the rule the whole adapter layer is built around. A missing scanner reported as "no findings" is worse than no scanner, because the first one gets believed.

  • Not installed → status missing, with an install hint.

  • Crashed or timed out → status failed, with the reason and the exact command to reproduce.

  • Output unparseable → status failed, not zero findings.

  • Marked required and absent → an error-severity finding, so the gate fails.

  • Either way, the summary carries adaptersNotRun and the report says so at the top level.

ml-skills own rules do not inspect a lockfile at all. A clean run says nothing about the CVEs in your dependencies — only the Trivy adapter covers those, and adapter_status will tell an agent so before it promises a security verdict.

Overlap is expected

hadolint's DL3025 and this package's INF012 both flag shell-form CMD. That is fine — two tools agreeing is not a bug — but if the duplication is noisy, silence one side in config rather than dropping the adapter.

Not over HTTP

Adapters run from the CLI and the stdio MCP server only. POST /v1/check never shells out: a service that executed local binaries on request content would be a remote code execution surface, and the HTTP face already refuses to accept a path for the same reason.

Behavioural evals

A standard is a behavioural intervention, not a document. evals/ tests whether it actually changes what gets written:

ml-skills eval            # all scenarios
ml-skills eval EV001      # one

Each scenario records the verbatim excuse an agent makes when the standard is absent, names the rule that counters it, and pairs it with the code that excuse produces plus the corrected version:

✓ EV001 events-messaging  Publishing an event inside the transaction that may roll back
    ✓ counter rules exist
    ✓ fires on the rationalized code
    ✓ silent on the corrected code
    ✓ counter is documented in the skill
    ⏳ "I'll commit first and then publish, so the event only fires on success."
       countered by EVT003 · behavioural check pending

The method is borrowed, with thanks, from a-pavithraa/springboot-skills-marketplace (MIT). No code was copied — see NOTICE.md for the full provenance and the licences of every tool the adapters invoke. The difference is that there the counter is a sentence, so only a human or a model can judge whether it landed; here it is a rule, so three of the four checks run in CI.

The fourth never passes. Whether an agent reaches for the excuse when the standard is absent needs a model run, and is reported as pending — never as passing. A harness that scored itself on the three cheap checks and let the fourth be inferred would be measuring the wrong thing while sounding confident about it, which is the failure this package exists to prevent.

Two things the harness enforces on the scenarios themselves, because they are what make it worth doing: the excuse must be verbatim and plausible (an excuse rewritten to look foolish tests nothing), and a rule that fires on the corrected code is a false positive on the compliant spelling — worse than a missing rule, because it teaches people the checker is noise.

Workflows — what to do, in what order

Rules say what is wrong. Assets show what right looks like. Neither says what to do first, and an agent handed eleven standards and ninety assets has no ordering — so it reads whatever appeared first in the diff and reports whatever it happened to notice.

workflow_list                  # the procedures, with their "not for" clauses
workflow_get review-java       # the numbered steps

Workflow

Steps

For

review-java

4

A diff, module or PR — checker first, contract pass before correctness

create-java-service

6

Assess the shape, choose the structure, apply the foundations

java-persistence

5

Name the persistence problem, then pick the pattern

upgrade-java

5

Scan, then one migration at a time, each green before the next

They own no content — they sequence the standards, references and assets that already exist. Each is also an MCP prompt, so a client gets them as commands, and each carries a not-for clause, because a procedure applied to a task it does not fit produces confident, irrelevant output rather than an obvious failure.

Two tests keep them honest: steps must be numbered without gaps, and every asset or reference a workflow names must actually exist — a procedure that sends an agent looking for a missing file gets improvisation instead.

The shape is borrowed from a-pavithraa/springboot-skills-marketplace, whose SKILL.md files are procedures rather than rule lists. That comparison is where this gap became obvious — see NOTICE.md.

Reference documents and runnable examples

Each skill's SKILL.md is a router, not a dump — it carries the decisions and the rule table, and points at a reference loaded only when a change touches that ground:

Skill

Reference

entity-relationships

mongoose-schema-design.md — embed-vs-reference, indexes, money, the Date.now() trap

events-messaging

outbox-and-delivery.md — the dual write, at-least-once, dead letters, payload evolution

resilience-patterns

failure-and-degradation.md — backoff and jitter, breakers, silent fallbacks, graceful shutdown

observability

correlation-and-signals.md — the three signals, correlation, RED metrics, log levels

Every skill with a reference also ships complete, copyable implementations under assets/ — not fragments:

mongoose-schema.js · mongoose-queries.js

a schema passing every MNG rule; the query patterns

outbox-publisher.js · idempotent-consumer.js

the transactional outbox and its relay; a consumer that survives redelivery

resilient-client.js · graceful-shutdown.js

timeout + backoff + jitter + breaker composed; SIGTERM in the order that matters

logger.js · config.js

structured logging with correlation and redaction; environment validated at boot

Assets are partitioned by language — assets/java/, assets/node/, assets/shared/ — so adding a language is a new directory rather than a reshuffle. Each asset's real destination (src/main/java/..., db/migration/..., k8s/...) is derived from that, which matters because a checker only selects a file whose path matches. See STRUCTURE.md.

Every asset is checked against the standard it demonstrates, by a test, at the path it would occupy in a real repository. An example that violates the rule it teaches is worse than no example — it is the strongest signal a reader gets, and it points the wrong way. Adding that check exposed four rule precision bugs:

Rule

Was wrong because

CFG006

Fired on PRICING_API_KEY: z.string().min(16) — a schema declaration, not a secret, in the very file that validates configuration

RES001 / RES002

Used proximity, but backoff is a helper defined once at the top of a module and used further down, so every correct implementation was reported

MNG012

schema.index({...}) is declared past the schema body, well beyond any sane window

OBS010

A logger implementation names every level by definition

All four now use file scope or an exclusion rather than a proximity window.

Skill descriptions also carry a not-for line on all 11 skills. That clause is what stops a skill loading on every task, and it matters more than the trigger list.

Two kinds of rule

Code rules live in lib/check/*.mjs and need real structure: the OpenAPI $ref walk, the SQL DDL reader, the Kubernetes pod spec.

Declarative rules live in lib/rulesets/ and are data — a pattern, the files it applies to, and the proximity conditions under which it matters:

{
  id: 'RES001', skill: 'resilience-patterns', severity: 'error',
  files: ['**/*.ts', '**/*.java'],
  forbid: '\\b(?:retry|maxRetries)\\b',
  near: { pattern: 'backoff|exponential|jitter', lines: 10 },   // suppress when mitigated
  message: 'retry configured with no backoff strategy',
}

near means "fine as long as that is close by"; requireNear means "only matters when that is close by". Getting those the wrong way round makes a rule report the exact complement of what it means, which is a mistake this package shipped once and now has a test for.

Your own rules, without forking

The rules that matter most to you are the ones nobody else can ship. Put them in .mlskills.json:

{
  "customRules": [{
    "id": "ACME001",
    "skill": "backend-patterns",
    "severity": "error",
    "files": ["**/*.ts"],
    "forbid": "from ['\"]moment['\"]",
    "title": "No moment.js",
    "why": "Frozen upstream. Use Temporal or date-fns.",
    "message": "moment is banned — see ADR-014"
  }]
}

They run alongside the built-ins, appear in explain, and obey the same ratification gate: an org cannot grant itself an error-severity rule on a standard it has not ratified. Malformed custom rules throw at load rather than silently matching nothing — a rule that quietly matches nothing reports a clean repo, which is the worst possible failure for a checker.


Use it

As an MCP server (agents)

Claude Code

claude mcp add ml-skills -- npx -y -p @mlmcps/ml-skills ml-skills-mcp --root .

Cursor, VS Code, Windsurf, or any MCP client — in the client's MCP config:

{ "mcpServers": { "ml-skills": { "command": "npx",
    "args": ["-y", "-p", "@mlmcps/ml-skills", "ml-skills-mcp", "--root", "."] } } }

Remote / HTTP clients — run ml-skills-http and point the client at POST /mcp (MCP Streamable HTTP).

One package, three binaries. npx @mlmcps/ml-skills runs the CLI because that bin matches the package name; the other two need -p: npx -p @mlmcps/ml-skills ml-skills-mcp. There is no separate @mlmcps/ml-skills-mcp package to install.

Check which copy is running — the predictable failure when a team mixes a stale npx cache with a fresh install — with --version, which prints the version and the resolved path:

npx -p @mlmcps/ml-skills ml-skills-mcp --version

The server exposes three surfaces, because clients use different ones:

Surface

What

Tools

skill_list, skill_get, rule_explain, check_repo, check_content, ratify_status, adapter_status

Resources

mlskill://<skill>/SKILL.md, reference examples, the rule catalogue as JSON

Prompts

apply-<skill> for each standard, plus review-against-standards

check_repo reads from disk; check_content takes documents inline, for a file being drafted or a client with no filesystem access. Both return the same verdicts.

Everything is read-only and makes no network calls — a standards checker that phoned home would not survive its first security review, and it has to run in the pipeline that has no egress.

In a website

npx -p @mlmcps/ml-skills ml-skills-http --port 8787

Serves a browser UI at / (read the standards, paste a file, see the findings) and a JSON API:

GET /v1/skills · GET /v1/skills/:name

the standards, including full document text

GET /v1/rules · GET /v1/rules/:id

the rule catalogue and per-rule rationale

POST /v1/check

{ files: [{ path, text }], skills?, overrides? } → findings

POST /v1/ratify

which decisions are still shipped defaults

POST /mcp

MCP Streamable HTTP

GET /health · GET /ready

distinct, per this package's own infra standard

const res = await fetch('https://standards.example.com/v1/check', {
  method: 'POST',
  headers: { 'Content-Type': 'application/json' },
  body: JSON.stringify({ files: [{ path: 'openapi.yaml', text: spec }] }),
});
const { summary, findings } = await res.json();

The HTTP face deliberately does not accept a path to scan. A service that took a directory and walked it would be a directory-traversal oracle for anyone who could reach the port. Callers send content; disk access stays on the stdio transport, where the client already has the files.

In CI

- run: npx @mlmcps/ml-skills check . --json > standards.json

Exits 1 on any error-severity finding from a ratified standard, 0 otherwise.

As a library

import { check } from '@mlmcps/ml-skills';

const { findings, summary } = check(
  [{ path: 'openapi.yaml', text }],
  { overrides: { 'openapi-contract': { status: 'standard' } } },
);

check() is pure — content in, findings out, no filesystem and no network — which is why the same function backs the agent, the website and the pipeline. It runs in a browser bundle unchanged.


Making it yours

npx @mlmcps/ml-skills init      # writes .mlskills.json
npx @mlmcps/ml-skills ratify    # what nobody has signed off yet
{
  "openapi-contract": {
    "status": "standard",              // now it can fail a build
    "decisions": {
      "pagination": "offset",          // we disagree with the default, and that is fine
      "paginationParams": ["offset", "limit"],
      "versionPattern": "^/api/v[0-9]+/"
    },
    "severity": { "OAS002": "off" }    // we do not care about summaries
  },
  "ui-patterns": { "status": "proposed" }
}

Arrays replace rather than merge — an override of a list means instead of, not as well as.

Exclude paths with a top-level exclude (or per skill). Deliberately non-compliant fixtures are the common case, and a checker you have to ignore is a checker nobody reads:

{ "exclude": ["test/fixtures/**", "**/*.generated.ts"] }

This repo ships its own .mlskills.json doing exactly that, with all five standards ratified — npx @mlmcps/ml-skills check . on this package reports zero findings, and the two rules it turns off say in the file why.

Ratifying means three things: someone read the decision, someone agreed to it, and someone accepts that it can now break a build. See RATIFY.md for the checklist and the decisions that most need a human.


Honest limitations

  • Most built-in checks are text-based, not AST-based. Five standards have structural checkers; the rest are patterns over the right files with proximity conditions. For AST-level matching, run the Semgrep or ESLint adapter — that is what they are for.

  • Nothing built in inspects your dependencies. A clean run says nothing about CVEs in your lockfile. Use the Trivy adapter. Supporting TypeScript, JavaScript, Java and Kotlin with real parsers would be four dependencies and four things to keep current, and this package has none. What is checked is what survives being checked on text: import edges, forbidden calls, credential and log shapes. Type-level rules belong in your language's own linter, and this does not replace it.

  • The YAML reader is a subset. Block mappings, sequences, flow collections, block scalars and comments — enough for OpenAPI, k8s and compose. It does not resolve anchors or aliases, and says so in the findings rather than parsing them wrongly.

  • These checks find shapes, not semantics. check passing does not mean the design is good; it means it does not violate the specific things you wrote down. Design review is still a human job.

  • False positives exist and are reported as findings, not truths. Silence a rule with "severity": { "RULE": "off" } rather than working around it.


Development

npm test          # 83 tests, no dependencies, no network
npm run eval      # behavioural eval scenarios
npm run serve     # HTTP + web UI on :8787
npm run mcp       # stdio MCP server

The reference example in skills/openapi-contract/assets/endpoint.yaml is checked by the test suite against its own standard. If a rule changes and the example stops passing, the build fails — a standard whose own example violates it is worse than no standard.

MIT. @mlmcps/ml-skills.