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…

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.
Listed under AI Hub → Skills on Three.js Resources — a curated directory for Three.js AI tools.
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
code-audit counts violations across a whole project and lists the exact lines, so the agent reads only what is flagged.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 libraryFull catalog: skills/README.md
| Category | Covers | Representative skills |
|---|---|---|
models/claude/ | Protocols tuned to each Claude model's actual behavior | fable-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 OpenCode | opencode-all-models (routing), GLM, DeepSeek, Kimi, Qwen3-Coder, MiniMax, Devstral, MiMo, gpt-oss, local-small-models (≤32B self-hosted) |
workflow/ | Session/process discipline, model-independent | intent-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 volume | coding-standards, code-audit, comment-style (none / terse / descriptive — token-saving, asked at install) |
memory/ | Persistent knowledge across sessions | memory-bank (repo-committed context/decisions), memory-gardener (prunes/merges learnings) |
stack/ | Technology-specific discipline | threejs-scene (shaders, disposal, render hygiene), astro-page (convention-driven scaffolding) |
libraries/ | The author's own libraries | strata-css, triforge |
meta/ | Maintains the library itself | skill-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 default | system1-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 default | mcp-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 systemFull map: coding-standards/index.md
| Layer | What it is | Location |
|---|---|---|
| 1 — Universal global rules | One rule file per discipline, applies to every file of that type | css-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 rules | Partials 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 rules | Extends or explicitly overrides a universal rule (OVERRIDES [file] RULE [n]) | frameworks/ — currently Astro, Bootstrap, Strata CSS |
| Cross-cutting | Covers |
|---|---|
git-standards.md, versioning-standards.md | Commit/branch/PR conventions, package versioning |
seo-standards.md, accessibility-standards.md | SEO 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.md | The 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 |
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.
| Tool | What the agent gets | Runs | Cost | Verified |
|---|---|---|---|---|
stitch | UI prototyping with Google Stitch (screens, variants, design systems) before any code is written | hosted | free for now, limited | live: generated a mobile screen and a Three.js 3D widget |
local-vision | describe_image — reads screenshots, diagrams and photos for models that cannot see (Qwen3-VL-2B via llama.cpp) | local CPU/GPU | free | real OpenCode; ~7–12 s per image on an 8-core CPU |
image-gen | generate_image for websites and apps: Gemini "Nano Banana" → Cloudflare Workers AI FLUX.1 schnell → labelled placeholder | hosted | Cloudflare free tier ≈ 173 images/day; Gemini image models need billing | real OpenCode; live Cloudflare and Gemini quota paths |
prefilter | prefilter — the local Laya decision model ranks candidate files/tests before the agent reads them in full | local | free | real 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.
skills/ — and coding-standards/ if the project should carry the standards — into the project root.read skills/README.md and install
.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.
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.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.memory-gardener skill prunes accumulated knowledge.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.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.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.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.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 covers the new adaptive-thinking Haiku, its breaking API changes against 4.5 and how to route to it.package.json at the skill root so the CommonJS companion scripts keep running inside Astro, Vite and other "type": "module" projects.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.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.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.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-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 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.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.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-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.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.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.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)
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).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?
hooks/register.ts 82 lines1import 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}
82hooks/rules.ts 113 lines1// 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