SLOPSHOPPER

standards-chain

Denies the first Edit or Write of a file until its coding-standards chain has been read (index, discipline file, role partial, framework file), and again after…

newguard
★ 7v0.1.0MITupdated 2026-10-09AftabIbrahimKazi/ai-dev-kit/mods/standards-chain
A shopper browsing a rack in a slop shop
README

AI Dev Kit

Agent skills, optional MCP tools, and layered coding standards for AI-assisted development — Claude Code, OpenCode, Codex, and any coding agent that reads markdown.

Portable SKILL.md agent skills, framework-agnostic coding standards, and opt-in free/local MCP servers (UI prototyping, image reading, image generation, file pre-filtering) for teams building with AI pair programmers. One clone. Drop two folders into any project. Every session works your way — and gets better at it with use.

License: MIT GitHub stars GitHub forks Last commit


Listed under AI Hub → Skills on Three.js Resources — a curated directory for Three.js AI tools.

Featured on Three.js Resources


Quick start

git clone https://github.com/AftabIbrahimKazi/ai-dev-kit.git
cp -r ai-dev-kit/skills ai-dev-kit/coding-standards your-project/

On Windows PowerShell: Copy-Item -Recurse ai-dev-kit/skills, ai-dev-kit/coding-standards your-project/

Then open Claude Code, OpenCode, Codex or any agent that reads markdown in your-project and say:

read skills/README.md and install

The installer copies markdown files only: no packages, no build step. Full details: Install into a project.

Why use it

  • Consistent sessions. The agent loads the right skill and the right standards for each task and each file, so session ten behaves like session one.
  • Fewer wasted tokens. Skills load only when relevant, reads are targeted, cheap models take the cheap work, and a script finds violations so the agent does not have to.
  • Standards that are enforced, not just written down. Rules are testable, the agent self-checks before a commit, and gaps are flagged instead of improvised.
  • Cleanup of old or shared code. code-audit counts violations across a whole project and lists the exact lines, so the agent reads only what is flagged.

What's in the kit

The kit is two independent systems that share one install path. skills/ teaches an AI coding agent how to work — session discipline, model-specific behavior, memory, coordination. coding-standards/ teaches it what correct code looks like for this project — language rules, file-role conventions, framework overrides. Either can be adopted alone; together, the coding-standards skill is what loads and enforces the standards chain during a session, so a project that wants enforcement needs both folders.

Every skill is a single portable markdown file with name + description frontmatter — the description is trigger-rich ("trigger when…") so it loads only when relevant — and reads a project-local, gitignored learnings.md sidecar at start and appends a lesson at end, so this repo stays the clean upstream while each install compounds its own experience. Every standard is testable and declarative, with wrong/right examples wherever a rule could be misread, and flags a gap (RULE AI-12) rather than inventing a rule where the standards are silent. They interlock at one seam: the install-kit skill installs both in one pass, and the coding-standards skill is what actually loads and walks the standards chain during a session — without it, coding-standards/ is just reference documentation.

skills/ — the self-improving skills library

Full catalog: skills/README.md

CategoryCoversRepresentative skills
models/claude/Protocols tuned to each Claude model's actual behaviorfable-5-1, opus-5-5, sonnet-5-5, haiku-5-5 (current) plus fable-5, opus-5, opus-4-8, sonnet-5, haiku-4-5 (previous gen), claude-all-models (fleet routing), opus-as-fable, hooks-enforcement (opt-in Claude Code hooks)
models/opencode/Open-weight fleet driven through OpenCodeopencode-all-models (routing), GLM, DeepSeek, Kimi, Qwen3-Coder, MiniMax, Devstral, MiMo, gpt-oss, local-small-models (≤32B self-hosted)
workflow/Session/process discipline, model-independentintent-capture → plan-first → interpretation-checkpoint → pre-merge-gate / pre-commit pipeline; plus handover, debug-protocol, session-budget, perf-audit, role-session (parallel sessions), e2e-scaffold
standards/Loads and enforces the coding-standards chain; audits a whole project against the script-checkable rules; sets comment volumecoding-standards, code-audit, comment-style (none / terse / descriptive — token-saving, asked at install)
memory/Persistent knowledge across sessionsmemory-bank (repo-committed context/decisions), memory-gardener (prunes/merges learnings)
stack/Technology-specific disciplinethreejs-scene (shaders, disposal, render hygiene), astro-page (convention-driven scaffolding)
libraries/The author's own librariesstrata-css, triforge
meta/Maintains the library itselfskill-writer (quality bar), install-kit (installer), skill-scope (pinned/archivable/addon classification), mod-writer and mod-setup (Claude Code mods)
addons/Optional, user-opted capabilities — never installed by defaultsystem1-prefilter (typed-decision prefiltering; hosted endpoint or the free local Laya model, exposed as the prefilter MCP tool)
mcp/Optional MCP servers behind one meta skill — never installed by defaultmcp-manager (setup interview, global-first per-tool config, MCP-first-then-fallback) + one companion per server: stitch, local-vision, image-gen

coding-standards/ — the layered standards system

Full map: coding-standards/index.md

LayerWhat it isLocation
1 — Universal global rulesOne rule file per discipline, applies to every file of that typecss-standards.md, html-standards.md, and one script standard (js-, ts-, or js-and-ts-standards.md — never more than one per project)
2 — Universal file-role rulesPartials for each file role within a discipline (e.g. a CSS token file vs. an overlay file; a script's entry vs. orchestrator vs. controller file)matching {discipline}-standards/ subfolder
3 — Framework rulesExtends or explicitly overrides a universal rule (OVERRIDES [file] RULE [n])frameworks/ — currently Astro, Bootstrap, Strata CSS
Cross-cuttingCovers
git-standards.md, versioning-standards.mdCommit/branch/PR conventions, package versioning
seo-standards.md, accessibility-standards.mdSEO structure/schema; WCAG 2.1 AA
qa/ (umbrella folder, not one file)Definition of done, branch gates, logic/error checks, security, E2E testing, bug reporting
ai-standards.mdThe AI behavioral contract — hallucination detection, [CX] context-integrity signal, read-efficiency rules — for every session regardless of tool
tooling/Lint configs that mechanically enforce whichever rules above are machine-checkable

Optional AI tools: free and local MCP servers for any coding agent

One setup interview (mcp-manager) wires any of these into Claude Code, OpenCode, or another MCP client. Models, runtimes, usage ledgers and keys live once in ~/.ai-dev-kit/ — never per project — and each tool falls back to your normal flow when it is missing, rate-limited, or out of quota.

ToolWhat the agent getsRunsCostVerified
stitchUI prototyping with Google Stitch (screens, variants, design systems) before any code is writtenhostedfree for now, limitedlive: generated a mobile screen and a Three.js 3D widget
local-visiondescribe_image — reads screenshots, diagrams and photos for models that cannot see (Qwen3-VL-2B via llama.cpp)local CPU/GPUfreereal OpenCode; ~7–12 s per image on an 8-core CPU
image-gengenerate_image for websites and apps: Gemini "Nano Banana" → Cloudflare Workers AI FLUX.1 schnell → labelled placeholderhostedCloudflare free tier ≈ 173 images/day; Gemini image models need billingreal OpenCode; live Cloudflare and Gemini quota paths
prefilterprefilter — the local Laya decision model ranks candidate files/tests before the agent reads them in fulllocalfreereal OpenCode; 1–3 s per call after a ~15–20 s first start

Built so a free tool can never become a trap. image-gen warns at 80% of a daily cap, switches provider at 90% (before anything fails or bills), pauses all generation until 00:00 UTC when every provider is exhausted, then serves clearly marked placeholders — and every switch, pause and placeholder is reported to you as a notice, in the tool output and in ~/.ai-dev-kit/image-gen/notices.log. Installers run a hardware preflight (CPU, RAM, disk, GPU) first, refuse installs the machine cannot hold, and pick CUDA/Vulkan/Metal builds only when the hardware warrants it, with automatic CPU fallback.

Honest status. Developed and tested on Windows 11 with an 8-core CPU and no discrete GPU. The CUDA, Metal, discrete-GPU Vulkan and Linux/macOS paths are implemented but untested. Model and API facts carry a verified: date and are re-checked live at setup. Credits and licences for every model and service (Qwen, llama.cpp, Laya, FLUX.1, Gemini, Stitch, placehold.co) are in CREDITS.md and each server file; nothing third-party is bundled in this repo.

Get started: install the kit, then tell your agent "set up MCP servers" — mcp-manager asks which tools, which AI clients, and where to keep keys, then writes the configs (merging, never overwriting) and health-checks each one.

Install into a project

  1. Copy skills/ — and coding-standards/ if the project should carry the standards — into the project root.
  2. Open Claude Code (or your AI coding tool) in that project and say:

read skills/README.md and install

  1. Choose everything or pick — the installer detects which coding tool you're in, copies the chosen skills to that tool's skill root (.claude/skills/ for Claude Code, .opencode/skills/ for OpenCode), wires the standards into that tool's session file (CLAUDE.md or AGENTS.md respectively), and reports what was installed.

Details, including per-skill manual installs: skills/README.md → Installing into a new project.

Key ideas

  • Self-improving skills. Every skill reads a learnings.md sidecar at start and appends one distilled lesson at end. Learnings are per-project (never committed here, never copied by the installer) — each project's copies adapt to that project.
  • Adaptive to change. Skills that describe living things (Claude models, libraries, frameworks) carry staleness guards: verify against the live source, follow it over the skill text, log the correction.
  • Standards as law, gaps flagged. The standards system is declarative and testable; where it is silent, the AI flags the gap instead of inventing a rule (RULE AI-12).
  • Sequential by default, parallel when you say so. The role-session skill coordinates multiple parallel AI sessions — Claude Code, OpenCode, or both (role charters, atomic mkdir file claims, a git commit token, session ids) and switches itself off in projects without the parallel structure.
  • Token-lean by design. Skill descriptions are hard-capped, bodies stay under ~120 lines, read-efficiency rules are part of the standards, and the memory-gardener skill prunes accumulated knowledge.
  • Intent before implementation, a self-check before handoff. intent-capture pins down goal/constraints/done-when on ambiguous asks before any plan is made; pre-merge-gate re-checks a diff against the loaded standards before a commit or review handoff — both are prose protocols, not tool-specific.
  • Measured, not promised. Where a claim has a number, the number and its sample size are stated: Haiku 5.5 running a written plan cost $0.049 against $0.581 for Sonnet 5.5 on six test plans (both passed all six); the audit script found the same lines as a model-led audit in 0.2 s with no tokens, where the model cost $0.026 and missed one file (one project, three rules); the output compactor shrank a verbose passing test log by 59% and failure output by about 0%. These are small samples from the author's own projects: indicative, not guarantees.
  • Claude Code enhancements stay optional and isolated. Where a Claude Code-only mechanism (like hook-based enforcement in hooks-enforcement) can mechanically assist a rule, it lives under skills/models/claude/ as an opt-in add-on — the underlying contract in coding-standards/ai-standards.md works the same with or without it, on any tool.

Recent additions

  • Project-wide audit. code-audit checks 46 script-checkable rules (CSS, HTML, JS/TS, page SEO/accessibility/performance basics, git history, versioning) across a whole project, groups violations by rule with the worst files first, lists file:line on request and keeps a baseline so counts can only go down. It also lists what it cannot check, so the agent knows what still needs reading.
  • Optional Claude Code mods. mods/ ships six opt-in mods installed through mod-setup: budget-ledger, precommit-gate, guard, edit-check, debug-nudge and standards-chain. Each wraps a tool-agnostic script or a few pure rules, fails open, and nothing in the kit depends on them.
  • Delegated execution in plan mode. plan-first can hand a written plan to the next cheaper model through delegate.js, with commit and push blocked for the executor, then verify and fix the result itself.
  • Haiku 5.5. haiku-5-5 covers the new adaptive-thinking Haiku, its breaking API changes against 4.5 and how to route to it.
  • Installs into ESM projects. The installer now adds a one-line package.json at the skill root so the CommonJS companion scripts keep running inside Astro, Vite and other "type": "module" projects.
  • MCP manager and free local/hosted tools. mcp-manager is a meta skill that owns setup interviews, per-client config writing (Claude Code, OpenCode, others), MCP-first-then-fallback routing and global-first layout for every optional MCP server; adding a server is one data file. Shipped servers: Stitch UI prototyping, local image reading (Qwen3-VL), image generation with provider fallback and limit protection, and the Laya-backed file prefilter. All run from one global home (~/.ai-dev-kit/) and were verified in a real OpenCode session.
  • Local decision model. system1-prefilter now supports a free offline provider (Laya, 421M parameters) with a hardware-aware installer and a prefilter MCP tool; measured guidance on phrasing, checkpoint choice and thresholds is recorded in the skill.
  • Controlled-writing rule. skill-writer now asks for imperative steps, one term per concept and explicit conditions (borrowed from ASD-STE100's disambiguation rules). Measured on two skills: about 16% fewer tokens with equal-or-better rule-following.
  • Kit audit. Every skill was checked for frontmatter, trigger-rich descriptions, length, catalog coverage and classification; stale descriptions and an install-kit contradiction were fixed.
  • Tool-routed installs. install-kit now detects the target tool and routes both the skill root and the session-protocol file (CLAUDE.md for Claude Code, AGENTS.md for OpenCode/Codex) — a contract in a file the tool never opens can no longer pass as a successful install. Parallel-session claims moved from a shared locks.md table to atomic handover/locks.d/ claims with per-session ids, so concurrent Claude Code and OpenCode sessions can't silently clobber each other.
  • Agent-tool discipline. agent-usage defaults every session to inline work — no Agent-tool delegation — unless the user names an agent explicitly or a scope-anchored need is judged and approved first; mode-kernel is the shared table governing how that gate (and three other skills' own stop-and-wait gates) behaves across autonomous, planning, and background session modes.
  • Skill ablation. skill-ablation is a periodic companion to memory-gardener: archive the accumulated session-protocol file/skills/hooks, run real work with none of it, and restore only what repeated real-world evidence proves is still needed — catching obsolete instructions a line-count cap alone can't.
  • Live-pulled Shopify skills. shopify-toolkit-install clones Shopify's own AI toolkit straight into a target project's skill root at install time — this kit never stores or forks Shopify-authored files, so authorship and their telemetry hook stay exactly where they belong.
  • Tool-compatibility checkpoint. Any skill that depends on a mechanism only one AI coding tool provides (Claude Code hooks, for example) now declares it structurally via a compat: <tool>-only frontmatter field, checked automatically by install-kit at install time — so tool-specific features get flagged and kept out of incompatible projects instead of failing silently.
  • Comment level. comment-style sets one project-wide level for AI-written code comments — none (default), terse, or descriptive — because comments cost output tokens when written and input tokens on every re-read. install-kit asks at install; the user can change it any time in-session. Rule-mandated comments (TS as justifications, suppression-directive reasons, tool pragmas) still apply at none.
  • Claude 5.5 generation. sonnet-5-5, opus-5-5, opus-5, and fable-5-1 join the lineup, and claude-all-models now routes across the current gears with the previous generation kept for pinned projects. These stay universal (no compat field) — they encode model behavior and API rules, not any one tool's mechanisms — and each new-generation skill flags the breaking API changes (forced tool_choice rejected, thinking blocks bound to model + conversation) that bite when moving up from its predecessor.
  • Opt-in addons. system1-prefilter introduces a fourth skill-scope category — a capability that's never stack-detected or installed by default, only turned on when the user explicitly asks, then stays pinned as their own durable choice. skill-scope now tracks this category alongside permanent-pinned and per-session-archivable.

Repository layout

skills/
  README.md        ← catalog + install instructions (start here)
  models/          ← per-model protocols + fleet routing (claude/ lineup + Claude Code hooks, opencode/ open-weight)
  workflow/        ← intent capture, planning, handover, debugging, budget, commits, pre-merge checks, perf, agent-usage discipline, session-mode gating, parallel sessions, e2e scaffolding
  standards/       ← the coding-standards enforcement skill
  memory/          ← repo-committed memory, knowledge gardening, periodic skill ablation
  stack/           ← Three.js, Astro, Shopify (live-pulled from Shopify's own toolkit, never forked)
  libraries/       ← skills for the author's own libraries (strata-css, triforge)
  meta/            ← skill-writer (quality bar), install-kit (installer), skill-scope (pin/archive/addon classification)
  addons/          ← opt-in, user-opted capabilities, never installed by default (system1-prefilter + local Laya)
  mcp/             ← opt-in MCP servers: mcp-manager + one companion per server (stitch, local-vision, image-gen)
mods/              ← optional Claude Code mods (budget-ledger, precommit-gate, guard, edit-check, debug-nudge, standards-chain), a local marketplace; each wraps a tool-agnostic script from a skill
tests/             ← tests for the kit's scripts (node tests/<name>.test.js) plus real captured logs under fixtures/
migrations/
  RENAMES.md       ← skill rename ledger — install-kit reads it to migrate old installs (never delete)
coding-standards/
  index.md         ← system map: layers, reading order, file-to-role mapping (start here)
  *-standards.md   ← global rules per discipline (css, html, js/ts, git, seo, a11y, qa, ai, …)
  */               ← file-role partials per discipline
  frameworks/      ← framework additions/overrides (astro, bootstrap)
  tooling/         ← lint configs enforcing the machine-checkable rules
  CLAUDE.example.md← session-protocol template — wire into CLAUDE.md (Claude Code) or AGENTS.md (OpenCode)

Maintaining the kit

  • This repo is the canonical home. Improve skills here, then re-run the install into projects; the installer diffs before overwriting so locally-evolved copies are never silently clobbered.
  • New skills follow the quality bar in skills/meta/skill-writer.md.
  • Standards edits follow the repo's own principles (index.md → Principles): testable, declarative, wrong/right examples mandatory. Machine-checkable rule changes update coding-standards/tooling/ in the same commit.
  • learnings.md files are gitignored — they belong to the project that earned them. Lessons worth keeping forever get promoted into skill bodies (see memory-gardener).

FAQ

What is an agent skill (SKILL.md)? A markdown file with a short description of when it applies and the steps to follow. The agent reads the description, and loads the file only when a task matches. Every skill here is one such file.

Which AI coding tools does it work with? Claude Code natively, plus OpenCode, Codex and any agent that reads markdown instructions. The installer detects the tool and puts the skills and the session file where that tool actually reads them (CLAUDE.md or AGENTS.md).

Does it install packages or touch my code? No. The installer copies markdown files only. The audit script and the Claude Code mods are optional and run only when you choose them.

Can I use only the standards, or only the skills? Yes, either folder works alone. A project that wants the standards enforced during a session needs both, because the coding-standards skill is what loads the chain.

How is this different from a prompt library? Skills load on a trigger instead of sitting in every prompt, they record lessons per project, the standards are testable rules with wrong/right examples, and a script audits a codebase against them.

**How do I update an install?

Source 2 files
hooks/register.ts 82 lines
1import type { Register } from 'claude-code'
2import { chainFor, declaredStd, denyMessage, missing, norm, relTo, type Config, type Std } from './rules'
3
4// Gate: the first Edit/Write of a file is denied until the model has Read that file's coding-standards chain
5// (index, discipline file, role partial when the path shows the role, framework file). Does nothing when the
6// project has no coding-standards/ folder. Deliberate deny; fails open on its own errors. Never touches handover/.
7type Project = { cwd: string; cfg: Config; declared?: Std; active: boolean }
8let project: Project | null = null
9const reads = new Map<string, Set<string>>() // who read what: 'main' or a subagent id
10
11async function load($: any): Promise<Project> {
12  if (project) return project
13  const cwd = norm(await $.session.cwd())
14  let cfg: Config = {}
15  try {
16    if (await $.fs.exists('.claude/standards-chain.json')) cfg = JSON.parse(await $.fs.read('.claude/standards-chain.json')) as Config
17  } catch {
18    cfg = {} // a broken config file leaves the defaults
19  }
20  const dir = norm(cfg.dir ?? 'coding-standards')
21  let declared: Std | undefined
22  if (cfg.script === undefined) {
23    for (const f of ['CLAUDE.md', 'AGENTS.md']) {
24      try {
25        if (await $.fs.exists(f)) declared = declaredStd(await $.fs.read(f)) ?? declared
26      } catch { /* an unreadable protocol file just means no declaration */ }
27    }
28  }
29  project = { cwd, cfg, declared, active: cfg.mode !== 'off' && (await $.fs.exists(`${dir}/index.md`)) }
30  return project
31}
32
33export const register: Register = (on) => {
34  on('session.start', async ($, e, next) => {
35    project = null // re-read config and declaration on each session start and reload
36    reads.clear()
37    return next(e)
38  }).catch(($, e, next) => next(e))
39
40  // After a compaction the standards text has left the context, so the chain must be read again.
41  // `precompute` only prepares a summary (a later trigger applies it), and a skip changes nothing.
42  on('session.compact', async ($, e, next) => {
43    const r = await next(e)
44    try {
45      if (e.trigger !== 'precompute' && !('skip' in r && r.skip)) reads.delete(e.agentId ? String(e.agentId) : 'main')
46    } catch { /* fail open */ }
47    return r
48  }).catch(($, e, next) => next(e))
49
50  on('tool.call', { tool: 'Read' }, async ($, e, next) => {
51    const ran = await next(e)
52    try {
53      if ('deny' in ran && ran.deny) return ran
54      if ('isError' in ran && ran.isError) return ran
55      const p = norm(e.file_path)
56      if (!/\.md$/i.test(p)) return ran
57      const who = 'agentId' in e && e.agentId ? String(e.agentId) : 'main'
58      const pr = await load($)
59      const abs = /^([a-z]:)?\//i.test(p) ? p : `${pr.cwd}/${p.replace(/^\.\//, '')}`
60      if (!reads.has(who)) reads.set(who, new Set())
61      reads.get(who)!.add(abs)
62    } catch { /* tracking must never touch the read */ }
63    return ran
64  }).catch(($, e, next) => next(e))
65
66  on('tool.call', { tool: ['Edit', 'Write'] }, async ($, e, next) => {
67    try {
68      const pr = await load($)
69      if (!pr.active) return next(e)
70      const rel = relTo(pr.cwd, e.file_path)
71      const chain = rel ? chainFor(rel, pr.cfg, pr.declared) : null
72      if (!rel || !chain) return next(e)
73      const who = 'agentId' in e && e.agentId ? String(e.agentId) : 'main'
74      const todo = missing(chain.files, reads.get(who) ?? [], pr.cfg)
75      if (todo.length) return { deny: denyMessage(rel, chain, todo, pr.cfg) }
76    } catch {
77      return next(e) // a broken gate must not block unrelated work
78    }
79    return next(e)
80  }).catch(($, e, next) => next(e))
81}
82
hooks/rules.ts 113 lines
1// Pure rules for standards-chain: which coding-standards files must be read before a given file is edited.
2// The layering and the role names come from coding-standards/index.md (File-to-Role Mapping). This file only
3// guesses a role from the path; when it is not sure it asks for layer 1 and leaves the role to the model.
4export type Std = 'js' | 'ts' | 'js-and-ts'
5export type Config = { mode?: 'deny' | 'off'; dir?: string; script?: Std; frameworks?: string[]; ignore?: string[] }
6export type Chain = { files: string[]; role: string | null }
7
8const BACK = String.fromCharCode(92)
9export const norm = (p: string) => p.split(BACK).join('/')
10
11const CSS = /\.(css|scss|sass|less|pcss)$/i
12const HTML = /\.(html?|astro)$/i
13const JS = /\.(js|jsx|mjs|cjs)$/i
14const TS = /\.(ts|tsx|mts|cts)$/i
15const SKIP = /(^|\/)(node_modules|dist|build|vendor|\.git|\.claude|handover|\.next|\.astro)\//i
16
17export function globToRegExp(g: string): RegExp {
18  const s = norm(g)
19    .replace(/[.+^${}()|[\]\\]/g, '\\$&')
20    .replace(/\*\*\//g, '\u0001').replace(/\*\*/g, '\u0002').replace(/\*/g, '[^/]*')
21    .replace(/\u0001/g, '(?:.*/)?').replace(/\u0002/g, '.*')
22  return new RegExp('(^|/)' + s + '$')
23}
24
25// Path relative to the project, or null when it is outside it.
26export function relTo(cwd: string, p: string): string | null {
27  const c = norm(cwd).replace(/\/+$/, '')
28  const f = norm(p)
29  if (f.toLowerCase().startsWith(c.toLowerCase() + '/')) return f.slice(c.length + 1)
30  if (/^([a-z]:)?\//i.test(f)) return null
31  return f.replace(/^\.\//, '')
32}
33
34// 'js-and-ts' / 'ts' / 'js' when exactly one script standard is named in the session-protocol text.
35export function declaredStd(text: string): Std | undefined {
36  if (/js-and-ts-standards/i.test(text)) return 'js-and-ts'
37  const ts = /(^|[^-\w])ts-standards/i.test(text)
38  const js = /(^|[^-\w])js-standards/i.test(text)
39  return ts && !js ? 'ts' : js && !ts ? 'js' : undefined
40}
41
42function cssRole(rel: string, base: string): string | null {
43  if (/(token|variable|theme|palette|colou?rs?)/.test(base)) return 'token-files'
44  if (/layout/.test(base)) return 'layout-files'
45  if (/(modal|drawer|overlay|dialog)/.test(base)) return 'overlay-files'
46  if (/(^|[-_.])(utils?|utilit(y|ies)|helpers?|atomic)([-_.]|$)/.test(base)) return 'utility-files'
47  if (/(^|\/)components?\//.test(rel)) return 'component-files'
48  return null
49}
50
51function htmlRole(rel: string, base: string): string | null {
52  if (/(^|\/)layouts?\//.test(rel) || /^layout/.test(base)) return 'layout-templates'
53  if (/(^|\/)pages?\//.test(rel)) return 'page-files'
54  if (/(^|\/)components?\//.test(rel)) return 'component-files'
55  return null
56}
57
58function scriptRole(rel: string, base: string, std: Std): string | null {
59  if (std !== 'js' && (/\.d\.ts$/i.test(rel) || /^types?$/.test(base) || /(^|\/)types\//.test(rel))) return 'type-files'
60  if (/orchestrator/.test(base)) return 'orchestrator-files'
61  if (/controller/.test(base)) return 'controller-files'
62  if (/preset/.test(base)) return 'preset-files'
63  if (/(^|[-_.])(utils?|utilit(y|ies)|helpers?)([-_.]|$)/.test(base) || /(^|\/)(utils|helpers)\//.test(rel)) return 'utility-files'
64  if (/^(main|entry|bootstrap|app)$/.test(base)) return 'entry-files'
65  if (/(engine|class)$/.test(base)) return 'class-files'
66  return null
67}
68
69// The standards files to read before editing `rel` (paths relative to the standards folder), or null when
70// the file has no standard (other languages, generated folders, the standards themselves).
71export function chainFor(rel: string, cfg: Config = {}, declared?: Std): Chain | null {
72  const dir = norm(cfg.dir ?? 'coding-standards').replace(/\/+$/, '')
73  const low = rel.toLowerCase()
74  if (SKIP.test(low) || low === dir.toLowerCase() || low.startsWith(dir.toLowerCase() + '/')) return null
75  if (/\.min\.(js|css)$/.test(low) || (cfg.ignore ?? []).some((g) => globToRegExp(g).test(rel))) return null
76  const base = (low.split('/').pop() ?? '').replace(/\.d\.ts$/, '').replace(/\.[^.]+$/, '')
77  let files: string[]
78  let role: string | null
79  if (CSS.test(low)) {
80    role = cssRole(low, base)
81    files = ['index.md', 'css-standards.md', ...(role ? [`css-standards/${role}.md`] : [])]
82  } else if (HTML.test(low)) {
83    role = htmlRole(low, base)
84    files = ['index.md', 'html-standards.md', ...(role ? [`html-standards/${role}.md`] : [])]
85  } else if (JS.test(low) || TS.test(low)) {
86    const std: Std = cfg.script ?? declared ?? (TS.test(low) ? 'ts' : 'js')
87    role = scriptRole(low, base, std)
88    files = ['index.md', `${std}-standards.md`, ...(role ? [`${std}-standards/${role}.md`] : [])]
89  } else {
90    return null
91  }
92  const fw = new Set((cfg.frameworks ?? []).map((f) => f.toLowerCase()))
93  if (/\.astro$/.test(low)) fw.add('astro')
94  for (const f of fw) files.push(`frameworks/${f}.md`)
95  return { files, role }
96}
97
98// Chain files not yet read. `reads` holds normalised absolute paths of files the model has read.
99export function missing(files: string[], reads: Iterable<string>, cfg: Config = {}): string[] {
100  const dir = norm(cfg.dir ?? 'coding-standards').replace(/\/+$/, '').toLowerCase()
101  const have = [...reads].map((r) => norm(r).toLowerCase())
102  return files.filter((f) => !have.some((r) => r.endsWith(`/${dir}/${f.toLowerCase()}`)))
103}
104
105export function denyMessage(rel: string, chain: Chain, todo: string[], cfg: Config = {}): string {
106  const dir = norm(cfg.dir ?? 'coding-standards').replace(/\/+$/, '')
107  const list = todo.map((f) => `${dir}/${f}`).join(', ')
108  const role = chain.role
109    ? ` Role guessed from the path: ${chain.role.replace(/-/g, ' ')}.`
110    : ' Role not clear from the path: after index.md, pick it in the File-to-Role table and read that partial too.'
111  return `standards-chain: read these with Read before editing ${rel}, then retry: ${list}.${role} Off: disable the mod in /plugin, or set {"mode":"off"} in .claude/standards-chain.json.`
112}
113