Skip to content

Commit 6093a46

Browse files
committed
fix: resolve CLI tooling issues and enforce tag casing conventions
- turbo.json: remove non-existent globalDependencies (oxfmt.config.ts, oxlint.config.ts) — these never existed; replace with vite.config.ts which is the actual unified fmt/lint config used by vite-plus - CLAUDE.md: fix all references to tsdown.config.ts → vite.config.ts and 'tsdown' build tool description → 'vp pack' (vite-plus command) - check-tags.ts: add title-case tag detection alongside existing ALLCAPS check; camelCase API identifiers (useFetch, runtimeConfig) are still allowed; raises WARN for tags like Windows, Homebrew, Systray - nextdns-cli/SKILL.md: fix link labels to use kebab-case ('Conditional Profiles' → 'conditional-profiles', 'Advanced Linux Support' → 'linux-advanced') per repository naming convention - nextdns-cli rules: lowercase all title-case and ALLCAPS tags across windows-installation, macos-installation, platform-specific, docker- deployment, advanced-features, ddwrt-installation, profile-configuration, system-configuration, troubleshooting - nextdns-api rules: lowercase tags in authentication, date-formats, logs-streaming, nested-endpoints, profile-management, security-settings - nextdns-ui rules: lowercase hostsVN → hostsvn, Hagezi → hagezi, TLDs → tlds in privacy-settings and security-settings - nextdns-frontend rules: lowercase ErrorBoundary → error-boundary in react-router/error-handling - Rebuild all AGENTS.md to reflect updated tag values
1 parent 51e0b5f commit 6093a46

27 files changed

Lines changed: 448 additions & 99 deletions

CLAUDE.md

Lines changed: 0 additions & 1 deletion
This file was deleted.

CLAUDE.md

Lines changed: 326 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,326 @@
1+
# AGENTS.md
2+
3+
<!-- @case-police-ignore Api -->
4+
5+
## Repository overview
6+
7+
NextDNS Skills is a structured knowledge collection for AI agents, enabling complex operations
8+
across the NextDNS ecosystem via domain-specific context injection:
9+
10+
- **NextDNS API**: Programmatic configuration, analytics, and log management.
11+
- **NextDNS CLI**: Deployment, system configuration, and monitoring.
12+
- **NextDNS Web UI**: Strategic configuration and dashboard-based management.
13+
- **Integrations**: Third-party platform connections (OpenWrt, pfSense, Tailscale, and more).
14+
- **NextDNS Frontend**: Nuxt 4, Next.js 15, Astro, SvelteKit, and React Router v7 patterns.
15+
16+
## Repository architecture
17+
18+
```text
19+
nextdns-skills/
20+
├── skills/
21+
│ ├── nextdns-api/ # 23 rules — API protocols and endpoints
22+
│ │ ├── SKILL.md
23+
│ │ └── rules/
24+
│ ├── nextdns-cli/ # 24 rules — Deployment and system config
25+
│ │ ├── SKILL.md
26+
│ │ └── rules/
27+
│ ├── nextdns-ui/ # 16 rules — Web dashboard strategy
28+
│ │ ├── SKILL.md
29+
│ │ └── rules/
30+
│ ├── integrations/ # 20 rules — Platform connectivity
31+
│ │ ├── SKILL.md
32+
│ │ └── rules/
33+
│ └── nextdns-frontend/ # 35 rules — Frontend frameworks
34+
│ ├── SKILL.md
35+
│ └── rules/
36+
│ ├── astro/
37+
│ ├── nextjs/
38+
│ ├── nuxt/
39+
│ ├── react-router/
40+
│ └── sveltekit/
41+
├── packages/
42+
│ ├── nextdns-scripts/ # Validation and maintenance scripts
43+
│ │ ├── bin/
44+
│ │ │ └── nextdns-skills-scripts.js # Static entrypoint (checked into git)
45+
│ │ ├── src/
46+
│ │ │ ├── cli.ts
47+
│ │ │ ├── index.ts
48+
│ │ │ ├── validate-rules.ts
49+
│ │ │ ├── update-counts.ts
50+
│ │ │ ├── check-duplicates.ts
51+
│ │ │ ├── check-tags.ts
52+
│ │ │ ├── generate-stats.ts
53+
│ │ │ └── utils.ts
54+
│ │ ├── tsconfig.json
55+
│ │ ├── vite.config.ts
56+
│ │ └── vitest.config.ts
57+
│ └── nextdns-skills-build/ # Build tooling and programmatic API
58+
│ ├── bin/
59+
│ │ └── nextdns-skills-build.js # Static entrypoint (checked into git)
60+
│ ├── src/
61+
│ │ ├── cli.ts
62+
│ │ ├── index.ts
63+
│ │ ├── build.ts
64+
│ │ ├── validate.ts
65+
│ │ ├── parser.ts
66+
│ │ ├── config.ts
67+
│ │ ├── types.ts
68+
│ │ ├── search.ts
69+
│ │ ├── export.ts
70+
│ │ ├── extract-tests.ts
71+
│ │ ├── migrate.ts
72+
│ │ └── utils.ts
73+
│ ├── tsconfig.json
74+
│ ├── vite.config.ts
75+
│ └── vitest.config.ts
76+
├── templates/
77+
│ ├── rule-template.md
78+
│ └── skill-template.md
79+
├── data/schemas/
80+
│ └── profile.json
81+
├── tsconfig.json
82+
├── pnpm-workspace.yaml
83+
└── turbo.json
84+
```
85+
86+
## Package architecture
87+
88+
Both packages use a static `bin/` entrypoint checked into git that imports `dist/cli.mjs` at
89+
runtime. This means pnpm creates the `.bin` symlink during `pnpm install` before `dist/` is built
90+
— the same pattern Vite uses for `bin/vite.js`.
91+
92+
### `nextdns-scripts` (package name: `nextdns-skills-scripts`)
93+
94+
Maintenance scripts: validate rule integrity, sync rule counts, check duplicates and tags, print
95+
statistics.
96+
97+
**Exports:**
98+
99+
```json
100+
{
101+
"exports": {
102+
".": { "import": "./dist/index.mjs" }
103+
}
104+
}
105+
```
106+
107+
**CLI commands** (`nextdns-skills-scripts <command>`):
108+
109+
| Command | Description |
110+
| :--- | :--- |
111+
| `validate-rules` | Frontmatter and referential integrity |
112+
| `update-counts` | Sync rule counts in README.md |
113+
| `check-duplicates` | Duplicate title detection |
114+
| `check-tags` | Tag hygiene validation |
115+
| `generate-stats` | Statistics report (`--text`) |
116+
117+
**Package scripts:**
118+
119+
| Script | Description |
120+
| :--- | :--- |
121+
| `build` | `vp pack` — compile to `dist/` |
122+
| `validate-rules` | Run validate-rules via bin |
123+
| `update-counts` | Run update-counts via bin |
124+
| `check-duplicates` | Run check-duplicates via bin |
125+
| `check-tags` | Run check-tags via bin |
126+
| `generate-stats` | Run generate-stats via bin |
127+
| `test` | `vitest run` |
128+
| `test:coverage` | `vitest run --coverage` |
129+
| `types:check` | `tsc --noEmit` |
130+
131+
### `nextdns-skills-build`
132+
133+
Build tooling: compile rule files into `AGENTS.md`, validate, scaffold, search, and export rules.
134+
Also exposes a programmatic API.
135+
136+
**Exports:**
137+
138+
```json
139+
{
140+
"exports": {
141+
".": { "import": "./dist/index.mjs" },
142+
"./build": { "import": "./dist/build.mjs" },
143+
"./validate": { "import": "./dist/validate.mjs" },
144+
"./search": { "import": "./dist/search.mjs" },
145+
"./export": { "import": "./dist/export.mjs" }
146+
}
147+
}
148+
```
149+
150+
**CLI commands** (`nextdns-skills-build <command>`):
151+
152+
| Command | Description |
153+
| :--- | :--- |
154+
| `build` | Build AGENTS.md (`--all` or `--skill=<name>`) |
155+
| `validate` | Validate rule frontmatter and structure |
156+
| `search` | Search rules (`--query=`, `--tag=`, `--skill=`, `--impact=`, `--json`) |
157+
| `export` | Export rules to JSON/CSV (`--format=`, `--out=`, `--skill=`) |
158+
| `extract-tests` | Extract test cases from rules for LLM evaluation |
159+
| `migrate` | Scaffold a new rule file from template |
160+
161+
**Package scripts:**
162+
163+
| Script | Description |
164+
| :--- | :--- |
165+
| `build` | `vp pack` — compile to `dist/` |
166+
| `build-all` | Build AGENTS.md for all skills |
167+
| `build-api` | Build `nextdns-api` only |
168+
| `build-cli` | Build `nextdns-cli` only |
169+
| `build-frontend` | Build `nextdns-frontend` only |
170+
| `build-integrations` | Build `integrations` only |
171+
| `build-ui` | Build `nextdns-ui` only |
172+
| `validate` | Validate rule files |
173+
| `search` | Search rules |
174+
| `export` | Export rules to JSON or CSV |
175+
| `extract-tests` | Extract test cases |
176+
| `migrate` | Scaffold a new rule |
177+
| `test` | `vitest run` |
178+
| `test:coverage` | `vitest run --coverage` |
179+
| `types:check` | `tsc --noEmit` |
180+
181+
### TypeScript conventions
182+
183+
Both packages share one root `tsconfig.json` extended by each package. Enforced settings:
184+
185+
- `strict: true`, `noUncheckedIndexedAccess: true`, `exactOptionalPropertyTypes: true`
186+
- `verbatimModuleSyntax: true` — always use `import type` for type-only imports
187+
- Array/object index access returns `T | undefined` — guard with `?? fallback` or check first
188+
- Omit optional properties instead of assigning `undefined`
189+
- Forbidden: `any`, `object`, `Function`, non-null assertions (`!`) without a type guard, and
190+
`as T` casts without prior narrowing
191+
192+
Run `pnpm -F <package> types:check` before committing TypeScript changes.
193+
194+
## Skill development lifecycle
195+
196+
### Folder structure
197+
198+
```text
199+
skills/{category}/
200+
SKILL.md # Category manifest with keyword index
201+
rules/
202+
{rule-name}.md # kebab-case filename
203+
```
204+
205+
Category names and rule filenames are always `kebab-case`.
206+
207+
### Skill manifest (`SKILL.md`)
208+
209+
Use `templates/skill-template.md`. Required frontmatter:
210+
211+
- `name`: matches directory name exactly
212+
- `description`: 2–4 sentences with trigger keywords — critical for AI activation
213+
- `metadata`: `author` (`tuanductran`) and `version` (semantic)
214+
215+
Every rule file must be registered in either the **Capability** or **Efficiency** table in the
216+
manifest. Adding a rule without updating `SKILL.md` in the same commit is a protocol violation.
217+
218+
### Rule specifications
219+
220+
Use `templates/rule-template.md`. Required frontmatter:
221+
222+
| Field | Values |
223+
| :--- | :--- |
224+
| `title` | Exact match with H1 heading |
225+
| `impact` | `HIGH`, `MEDIUM`, or `LOW` |
226+
| `impactDescription` | One sentence — consequence of non-compliance |
227+
| `type` | `capability` or `efficiency` |
228+
| `tags` | 3–10 keywords, YAML array format |
229+
230+
Required sections in order: H1 heading (followed by a one-line description), `Overview`,
231+
`Correct usage` (✅), `Do NOT use` (❌), `Troubleshooting`, `Reference`.
232+
233+
## Protocol system
234+
235+
1. **Atomic commits**: a rule change and its `SKILL.md` update must be in the same commit.
236+
2. **Header casing**: use `X-Api-Key` only. Add `<!-- @case-police-ignore Api -->` at the top of
237+
any Markdown file referencing it.
238+
3. **Terminology**: `profile` (not configuration), `blocklist` (not blacklist), `allowlist` (not
239+
whitelist).
240+
4. **Zero-PII**: never commit real API keys or profile IDs — use `YOUR_API_KEY`, `abc123`,
241+
`example.com`.
242+
5. **Conventional commits**: `type(scope): description` — for example,
243+
`feat(api): add rewrite rule`.
244+
6. **Schema consistency**: sync structural changes with `data/schemas/profile.json`.
245+
246+
## Frontend skill standards
247+
248+
Applies to `skills/nextdns-frontend/` only. Frameworks: Nuxt 4, Next.js 15, Astro, SvelteKit,
249+
React Router v7.
250+
251+
All code examples must compile under:
252+
253+
```json
254+
{
255+
"compilerOptions": {
256+
"strict": true,
257+
"noUncheckedIndexedAccess": true,
258+
"exactOptionalPropertyTypes": true
259+
}
260+
}
261+
```
262+
263+
**Error handling**: catch values are `unknown` — narrow with `instanceof Error` before accessing
264+
`.message`. Surface errors via the framework's error mechanism (`error()`, `ErrorBoundary`,
265+
`useFormState`). Never swallow errors silently.
266+
267+
**Accessibility**: use semantic HTML, add `aria-label` to interactive elements without visible
268+
text, loading states need `aria-live="polite"` or `role="status"`, never convey state with color
269+
alone.
270+
271+
**Testing**: data-fetching or mutation rules must include a Testing subsection with a mock of the
272+
NextDNS API call, one happy-path assertion, and one error-path assertion.
273+
274+
## Validation and quality assurance
275+
276+
Run before finalising any changes:
277+
278+
| Command | Purpose |
279+
| :--- | :--- |
280+
| `pnpm lint:fix` | Auto-fix formatting (`oxfmt`), code (`oxlint`), markdown, and syntax |
281+
| `pnpm lint:rules` | Validate frontmatter and referential integrity via Turbo |
282+
| `pnpm lint:all` | Full check including external link validation |
283+
| `pnpm check-duplicates` | Detect duplicate titles (ERROR within skill, WARN across) |
284+
| `pnpm check-tags` | Validate tag count (3–10), uniqueness, and casing |
285+
| `pnpm update-counts` | Sync rule counts in README.md |
286+
| `pnpm types:check` | Type-check all packages via Turbo |
287+
| `pnpm test` | Run Vitest across both packages |
288+
| `pnpm test:coverage` | Run tests with v8 coverage report |
289+
290+
## Building AGENTS.md
291+
292+
After modifying rule files, rebuild the compiled output:
293+
294+
```bash
295+
pnpm build:skills # All skills
296+
pnpm build:api # nextdns-api only
297+
pnpm build:cli # nextdns-cli only
298+
pnpm build:ui # nextdns-ui only
299+
pnpm build:integrations # integrations only
300+
pnpm build:frontend # nextdns-frontend only
301+
```
302+
303+
## Content standards
304+
305+
Follow the [Atlassian content guidelines](https://atlassian.design/foundations/content).
306+
307+
- Sentence case for all headings
308+
- Active voice — lead with verbs
309+
- No abbreviations (`for example`, not `e.g.`; `that is`, not `i.e.`)
310+
- No trailing catch-alls (`and more`, not `etc.`)
311+
- Spell out conjunctions (`and`, not `&`)
312+
313+
Fixed terminology:
314+
315+
| Use | Never use |
316+
| :-- | :-------- |
317+
| `profile` | configuration, config |
318+
| `blocklist` | blacklist, denylist |
319+
| `allowlist` | whitelist, passlist |
320+
| `X-Api-Key` | X-API-Key, x-api-key |
321+
322+
## Installation
323+
324+
**Claude Code**: `cp -r skills/{category} ~/.claude/skills/`
325+
326+
**claude.ai**: attach `SKILL.md` and relevant rules to the project context.

packages/nextdns-scripts/src/check-tags.ts

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -194,6 +194,31 @@ function validateTags(): boolean {
194194
message: `Unexpected ALLCAPS tags (use lowercase or known acronyms): [${allcapsTags.join(', ')}]`,
195195
});
196196
}
197+
198+
// Title-case single-word tags that are not camelCase API names (e.g. Windows, Homebrew, Systray)
199+
// camelCase (useFetch, runtimeConfig, createError, shouldRevalidate, ErrorBoundary) are allowed
200+
// as they are framework API identifiers.
201+
const titleCaseTags = tags.filter((t) => {
202+
const words = t.split(/[\s-]/);
203+
return words.some((w) => {
204+
if (w.length < 2) return false;
205+
const firstUpper = w[0] === w[0]?.toUpperCase() && w[0] !== w[0]?.toLowerCase();
206+
if (!firstUpper) return false;
207+
// Allow ALLCAPS (already caught above) and camelCase (has uppercase letter after position 0)
208+
const isCamelCase = /[A-Z]/.test(w.slice(1));
209+
if (isCamelCase) return false;
210+
// Allow known acronyms
211+
if (knownAcronyms.has(w.toLowerCase())) return false;
212+
return true;
213+
});
214+
});
215+
if (titleCaseTags.length > 0) {
216+
errors.push({
217+
file: rel,
218+
level: 'warn',
219+
message: `Title-case tags should be lowercase: [${titleCaseTags.join(', ')}]`,
220+
});
221+
}
197222
}
198223
}
199224

0 commit comments

Comments
 (0)