@contentrain/skills 0.1.2 → 0.2.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +65 -60
- package/dist/index.cjs +65 -1
- package/dist/index.d.cts +50 -2
- package/dist/index.d.cts.map +1 -1
- package/dist/index.d.mts +50 -2
- package/dist/index.d.mts.map +1 -1
- package/dist/index.mjs +65 -2
- package/dist/index.mjs.map +1 -1
- package/package.json +3 -1
- package/skills/contentrain/SKILL.md +149 -0
- package/skills/contentrain/references/architecture.md +212 -0
- package/skills/contentrain/references/content-formats.md +279 -0
- package/skills/contentrain/references/i18n.md +298 -0
- package/skills/contentrain/references/mcp-pipelines.md +195 -0
- package/skills/contentrain/references/mcp-tools.md +308 -0
- package/skills/contentrain/references/model-kinds.md +330 -0
- package/skills/contentrain/references/schema-types.md +177 -0
- package/skills/contentrain/references/security.md +146 -0
- package/skills/contentrain/references/workflow.md +285 -0
- package/skills/contentrain-bulk/SKILL.md +99 -0
- package/skills/contentrain-content/SKILL.md +162 -0
- package/skills/contentrain-diff/SKILL.md +62 -0
- package/skills/contentrain-doctor/SKILL.md +62 -0
- package/skills/contentrain-generate/SKILL.md +183 -0
- package/skills/contentrain-generate/references/generated-client.md +198 -0
- package/skills/contentrain-init/SKILL.md +99 -0
- package/skills/contentrain-model/SKILL.md +141 -0
- package/skills/contentrain-normalize/SKILL.md +185 -0
- package/skills/contentrain-normalize/references/extraction.md +164 -0
- package/skills/contentrain-normalize/references/reuse.md +146 -0
- package/skills/contentrain-normalize/references/what-is-content.md +115 -0
- package/skills/contentrain-quality/SKILL.md +180 -0
- package/skills/contentrain-quality/references/accessibility.md +160 -0
- package/skills/contentrain-quality/references/content-quality.md +299 -0
- package/skills/contentrain-quality/references/media.md +170 -0
- package/skills/contentrain-quality/references/seo.md +229 -0
- package/skills/contentrain-review/SKILL.md +170 -0
- package/skills/contentrain-sdk/SKILL.md +145 -0
- package/skills/contentrain-sdk/references/bundler-config.md +135 -0
- package/skills/contentrain-serve/SKILL.md +96 -0
- package/skills/contentrain-translate/SKILL.md +180 -0
- package/skills/contentrain-validate-fix/SKILL.md +92 -0
|
@@ -0,0 +1,164 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: extraction
|
|
3
|
+
description: "Phase 1 extraction details, rules, and examples"
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
# Phase 1: Extraction Details
|
|
7
|
+
|
|
8
|
+
Extraction pulls hardcoded content from source code into `.contentrain/` without modifying source files. Each extraction produces a single branch with all extracted content for review.
|
|
9
|
+
|
|
10
|
+
---
|
|
11
|
+
|
|
12
|
+
## Extraction Flow
|
|
13
|
+
|
|
14
|
+
### 1. Build Project Graph
|
|
15
|
+
|
|
16
|
+
```
|
|
17
|
+
contentrain_scan(mode: "graph")
|
|
18
|
+
```
|
|
19
|
+
|
|
20
|
+
Returns the import/component dependency graph. Use this to:
|
|
21
|
+
|
|
22
|
+
- Understand which components belong to which pages or features.
|
|
23
|
+
- Identify shared components vs page-specific components.
|
|
24
|
+
- Prioritize files by their role in the project (layout, page, component).
|
|
25
|
+
|
|
26
|
+
### 2. Find Candidates
|
|
27
|
+
|
|
28
|
+
```
|
|
29
|
+
contentrain_scan(mode: "candidates")
|
|
30
|
+
```
|
|
31
|
+
|
|
32
|
+
Returns hardcoded string candidates with file paths, line numbers, and surrounding context. Review candidates by file -- not all strings should be extracted. See [What Is Content](what-is-content.md) for filtering heuristics.
|
|
33
|
+
|
|
34
|
+
### 3. Agent Evaluation
|
|
35
|
+
|
|
36
|
+
This is the intelligence step -- the agent makes all decisions:
|
|
37
|
+
|
|
38
|
+
- **Filter false positives:** Remove CSS values, technical identifiers, import paths, variable names, config values, log messages, and test strings.
|
|
39
|
+
- **Assign domains:** Group candidates by domain (e.g., `marketing`, `blog`, `ui`, `system`).
|
|
40
|
+
- **Determine model types:** Choose the appropriate model kind for each group:
|
|
41
|
+
- **dictionary** -- UI strings, button labels, error messages. Flat key-value, all strings, no field schema.
|
|
42
|
+
- **singleton** -- Page-specific content with structured fields (hero section, features block).
|
|
43
|
+
- **collection** -- Repeating items with structured fields (testimonials, FAQs, team members).
|
|
44
|
+
- **document** -- Long-form content with markdown body and typed metadata (blog posts, docs).
|
|
45
|
+
- **Structure fields:** Define field names and assign candidate strings to fields.
|
|
46
|
+
|
|
47
|
+
### 4. Write Normalize Plan
|
|
48
|
+
|
|
49
|
+
After evaluation, write the plan as `.contentrain/normalize-plan.json`:
|
|
50
|
+
|
|
51
|
+
```json
|
|
52
|
+
{
|
|
53
|
+
"version": 1,
|
|
54
|
+
"status": "pending",
|
|
55
|
+
"created_at": "2026-03-16T12:00:00.000Z",
|
|
56
|
+
"agent": "claude",
|
|
57
|
+
"scan_stats": {
|
|
58
|
+
"files_scanned": 42,
|
|
59
|
+
"raw_strings": 320,
|
|
60
|
+
"candidates_sent": 85,
|
|
61
|
+
"extracted": 28,
|
|
62
|
+
"skipped": 57
|
|
63
|
+
},
|
|
64
|
+
"models": [
|
|
65
|
+
{
|
|
66
|
+
"id": "hero-section",
|
|
67
|
+
"kind": "singleton",
|
|
68
|
+
"domain": "marketing",
|
|
69
|
+
"i18n": true,
|
|
70
|
+
"fields": {
|
|
71
|
+
"title": { "type": "string", "required": true },
|
|
72
|
+
"subtitle": { "type": "string" }
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
],
|
|
76
|
+
"extractions": [
|
|
77
|
+
{
|
|
78
|
+
"value": "Build faster apps",
|
|
79
|
+
"file": "src/pages/index.vue",
|
|
80
|
+
"line": 12,
|
|
81
|
+
"model": "hero-section",
|
|
82
|
+
"field": "title"
|
|
83
|
+
}
|
|
84
|
+
],
|
|
85
|
+
"patches": [
|
|
86
|
+
{
|
|
87
|
+
"file": "src/pages/index.vue",
|
|
88
|
+
"line": 12,
|
|
89
|
+
"old_value": "Build faster apps",
|
|
90
|
+
"new_expression": "{{ $t('hero.title') }}"
|
|
91
|
+
}
|
|
92
|
+
]
|
|
93
|
+
}
|
|
94
|
+
```
|
|
95
|
+
|
|
96
|
+
This file is watched by the serve UI -- writing it triggers automatic detection.
|
|
97
|
+
|
|
98
|
+
### 5. Preview Extraction
|
|
99
|
+
|
|
100
|
+
```
|
|
101
|
+
contentrain_apply(mode: "extract", dry_run: true)
|
|
102
|
+
```
|
|
103
|
+
|
|
104
|
+
Returns a preview of what will be created in `.contentrain/` without making changes. Review the output:
|
|
105
|
+
|
|
106
|
+
- Verify models are structured correctly.
|
|
107
|
+
- Verify content assignments are accurate.
|
|
108
|
+
- Check for missing or misclassified candidates.
|
|
109
|
+
|
|
110
|
+
### 6. Execute Extraction
|
|
111
|
+
|
|
112
|
+
```
|
|
113
|
+
contentrain_apply(mode: "extract", dry_run: false)
|
|
114
|
+
```
|
|
115
|
+
|
|
116
|
+
Creates model definitions and content files in `.contentrain/` on a `contentrain/normalize/extract/{timestamp}` branch. `dry_run` defaults to `true`, so you MUST explicitly set `dry_run: false` to execute.
|
|
117
|
+
|
|
118
|
+
### 7. Validate and Submit
|
|
119
|
+
|
|
120
|
+
```
|
|
121
|
+
contentrain_validate
|
|
122
|
+
contentrain_submit
|
|
123
|
+
```
|
|
124
|
+
|
|
125
|
+
Submit always uses `review` mode for normalize operations.
|
|
126
|
+
|
|
127
|
+
---
|
|
128
|
+
|
|
129
|
+
## Extraction Rules
|
|
130
|
+
|
|
131
|
+
- Extraction creates content in `.contentrain/` but does NOT modify source files.
|
|
132
|
+
- Each extraction produces a single branch with all extracted content.
|
|
133
|
+
- Group related content into the fewest models that make semantic sense.
|
|
134
|
+
- Prefer **dictionary** kind for UI labels and error messages.
|
|
135
|
+
- Prefer **singleton** kind for page-specific content (hero, features).
|
|
136
|
+
- Prefer **collection** kind for repeating items (team members, FAQs, testimonials).
|
|
137
|
+
- Prefer **document** kind for long-form content with markdown body.
|
|
138
|
+
|
|
139
|
+
---
|
|
140
|
+
|
|
141
|
+
## Group Related Content Guidelines
|
|
142
|
+
|
|
143
|
+
When grouping candidates into models:
|
|
144
|
+
|
|
145
|
+
- Content from the same page section should go into the same model.
|
|
146
|
+
- Shared UI strings (navigation, footer, error messages) should be grouped into a single dictionary per domain.
|
|
147
|
+
- Do not create one model per component -- group related content into shared models.
|
|
148
|
+
- Use the project graph to understand component relationships and group accordingly.
|
|
149
|
+
- If multiple components share the same type of content (e.g., all cards have a title and description), consider a single collection model.
|
|
150
|
+
|
|
151
|
+
---
|
|
152
|
+
|
|
153
|
+
## Content Format Reference
|
|
154
|
+
|
|
155
|
+
Understand these formats before choosing model kinds:
|
|
156
|
+
|
|
157
|
+
| Kind | Format | Keys | Example Path |
|
|
158
|
+
|------|--------|------|-------------|
|
|
159
|
+
| Dictionary | Flat key-value JSON, all strings | Semantic dot-separated addresses | `{model-id}/{locale}.json` |
|
|
160
|
+
| Collection | Object-map JSON by 12-char hex ID | Auto-generated entry IDs | `{model-id}/{locale}.json` |
|
|
161
|
+
| Singleton | Single JSON object per locale | Typed field names | `{model-id}/{locale}.json` |
|
|
162
|
+
| Document | Markdown with frontmatter per slug | Slug-based file names | `{model-id}/{slug}/{locale}.md` |
|
|
163
|
+
|
|
164
|
+
Call `contentrain_describe_format` before deciding model structure to see the exact format for each kind.
|
|
@@ -0,0 +1,146 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: reuse
|
|
3
|
+
description: "Phase 2 reuse details, replacement expressions by stack, and patching rules"
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
# Phase 2: Reuse Details
|
|
7
|
+
|
|
8
|
+
Reuse patches source files to replace hardcoded strings with content references. Only start after Phase 1 (extraction) is reviewed and merged. Content must exist in `.contentrain/` before patching source files.
|
|
9
|
+
|
|
10
|
+
---
|
|
11
|
+
|
|
12
|
+
## Reuse Flow
|
|
13
|
+
|
|
14
|
+
### 1. Select Scope
|
|
15
|
+
|
|
16
|
+
List the extracted models and ask the user which model or domain to process first. Process one model or domain at a time to keep diffs small and reviewable.
|
|
17
|
+
|
|
18
|
+
### 2. Determine Replacement Expressions
|
|
19
|
+
|
|
20
|
+
Based on the project's tech stack, determine how source files should reference content. The agent determines the replacement expression -- MCP does exact string replacement only.
|
|
21
|
+
|
|
22
|
+
Also determine any necessary import statements that must be added to patched files (e.g., `import { useTranslation } from 'next-intl'`).
|
|
23
|
+
|
|
24
|
+
### 3. Preview Reuse
|
|
25
|
+
|
|
26
|
+
```
|
|
27
|
+
contentrain_apply(mode: "reuse", scope: { model: "<model-id>" }, patches: [...], dry_run: true)
|
|
28
|
+
```
|
|
29
|
+
|
|
30
|
+
The `scope` requires at least one of `model` or `domain`. The `patches` array contains the replacement instructions.
|
|
31
|
+
|
|
32
|
+
Review the dry-run output:
|
|
33
|
+
|
|
34
|
+
- Verify each string replacement is correct.
|
|
35
|
+
- Verify import statements will be added where needed.
|
|
36
|
+
- Confirm no non-content strings are being replaced.
|
|
37
|
+
- Check that component structure and behavior are preserved.
|
|
38
|
+
|
|
39
|
+
### 4. Execute Reuse
|
|
40
|
+
|
|
41
|
+
```
|
|
42
|
+
contentrain_apply(mode: "reuse", scope: { model: "<model-id>" }, patches: [...], dry_run: false)
|
|
43
|
+
```
|
|
44
|
+
|
|
45
|
+
`dry_run` defaults to `true`, so you MUST explicitly set `dry_run: false` to execute. This patches source files and creates a `contentrain/normalize/reuse/{model}/{timestamp}` branch.
|
|
46
|
+
|
|
47
|
+
### 5. Validate and Submit
|
|
48
|
+
|
|
49
|
+
```
|
|
50
|
+
contentrain_validate
|
|
51
|
+
contentrain_submit
|
|
52
|
+
```
|
|
53
|
+
|
|
54
|
+
Verify source files parse correctly after patching, content references resolve to existing entries, and no strings were missed or double-replaced.
|
|
55
|
+
|
|
56
|
+
### 6. Repeat
|
|
57
|
+
|
|
58
|
+
Repeat steps 1-5 for each remaining model or domain until all extracted content is referenced in source code.
|
|
59
|
+
|
|
60
|
+
---
|
|
61
|
+
|
|
62
|
+
## Replacement Expressions by Stack
|
|
63
|
+
|
|
64
|
+
| Stack | Template Pattern | Attribute Pattern | Script Pattern | Example |
|
|
65
|
+
|-------|-----------------|-------------------|----------------|---------|
|
|
66
|
+
| Vue/Nuxt (vue-i18n) | `{{ $t('key') }}` | `:attr="$t('key')"` | `t('key')` | `{{ $t('hero.title') }}` |
|
|
67
|
+
| React/Next (next-intl) | `{t('key')}` | `{t('key')}` | `t('key')` | `{t('hero.title')}` |
|
|
68
|
+
| React/Next (react-intl) | `{intl.formatMessage({id: 'key'})}` | `{intl.formatMessage({id: 'key'})}` | `intl.formatMessage({id: 'key'})` | `{intl.formatMessage({id: 'hero.title'})}` |
|
|
69
|
+
| Svelte/SvelteKit | `{$t('key')}` | `{$t('key')}` | `$t('key')` | `{$t('hero.title')}` |
|
|
70
|
+
| Astro | `{t('key')}` | `{t('key')}` | `t('key')` | `{t('hero.title')}` |
|
|
71
|
+
| SDK direct import | `{data.field}` | `{data.field}` | `query('model').first()` | Direct data access via `#contentrain` |
|
|
72
|
+
|
|
73
|
+
### Import Statements by Stack
|
|
74
|
+
|
|
75
|
+
When patching files, ensure the necessary imports or setup code are added:
|
|
76
|
+
|
|
77
|
+
| Stack | Required Import/Setup |
|
|
78
|
+
|-------|----------------------|
|
|
79
|
+
| Vue/Nuxt (vue-i18n) | `const { t } = useI18n()` in `<script setup>` |
|
|
80
|
+
| React/Next (next-intl) | `import { useTranslations } from 'next-intl'` + `const t = useTranslations()` |
|
|
81
|
+
| React/Next (react-intl) | `import { useIntl } from 'react-intl'` + `const intl = useIntl()` |
|
|
82
|
+
| Svelte/SvelteKit | `import { t } from '$lib/i18n'` (project-specific) |
|
|
83
|
+
| Astro | `import { t } from '@/i18n'` (project-specific) |
|
|
84
|
+
| SDK direct import | `import { query } from '#contentrain'` |
|
|
85
|
+
|
|
86
|
+
Detect the project's existing i18n setup and match its import conventions. Do not introduce new i18n libraries.
|
|
87
|
+
|
|
88
|
+
---
|
|
89
|
+
|
|
90
|
+
## Reuse Rules
|
|
91
|
+
|
|
92
|
+
1. **One model at a time.** Do NOT reuse the entire project in one operation. Keep diffs small and reviewable.
|
|
93
|
+
|
|
94
|
+
2. **Ensure i18n is configured.** The i18n/content library must be properly configured in the project before reuse. Check for existing i18n setup.
|
|
95
|
+
|
|
96
|
+
3. **Add necessary imports.** Every patched file must have the required import statements and setup code (composable import, hook call, etc.).
|
|
97
|
+
|
|
98
|
+
4. **Do NOT change component structure.** Only replace string literals with content references. Do not modify component behavior, layout, or logic.
|
|
99
|
+
|
|
100
|
+
5. **Include setup code.** If a source file requires setup code (composable import, hook call), include it in the patch.
|
|
101
|
+
|
|
102
|
+
6. **Handle attribute bindings.** When replacing strings in HTML attributes (e.g., `placeholder="Search"`), use the correct attribute binding syntax for the stack (e.g., `:placeholder="$t('search')"` in Vue).
|
|
103
|
+
|
|
104
|
+
7. **Preserve surrounding markup.** Do not add or remove HTML tags, change class names, or alter component props beyond the string replacement.
|
|
105
|
+
|
|
106
|
+
---
|
|
107
|
+
|
|
108
|
+
## Reuse Patch Example
|
|
109
|
+
|
|
110
|
+
```json
|
|
111
|
+
{
|
|
112
|
+
"mode": "reuse",
|
|
113
|
+
"scope": { "model": "ui-texts" },
|
|
114
|
+
"dry_run": false,
|
|
115
|
+
"patches": [
|
|
116
|
+
{
|
|
117
|
+
"file": "src/components/Header.vue",
|
|
118
|
+
"line": 15,
|
|
119
|
+
"old_value": "Sign in",
|
|
120
|
+
"new_expression": "{{ $t('ui.sign-in') }}"
|
|
121
|
+
},
|
|
122
|
+
{
|
|
123
|
+
"file": "src/components/Header.vue",
|
|
124
|
+
"line": 22,
|
|
125
|
+
"old_value": "Sign up",
|
|
126
|
+
"new_expression": "{{ $t('ui.sign-up') }}"
|
|
127
|
+
}
|
|
128
|
+
],
|
|
129
|
+
"imports": [
|
|
130
|
+
{
|
|
131
|
+
"file": "src/components/Header.vue",
|
|
132
|
+
"statement": "const { t } = useI18n()",
|
|
133
|
+
"location": "script-setup"
|
|
134
|
+
}
|
|
135
|
+
]
|
|
136
|
+
}
|
|
137
|
+
```
|
|
138
|
+
|
|
139
|
+
---
|
|
140
|
+
|
|
141
|
+
## Edge Cases
|
|
142
|
+
|
|
143
|
+
- **Strings in script blocks:** Use the script pattern (no template delimiters). Example: `const label = t('key')` not `const label = {{ $t('key') }}`.
|
|
144
|
+
- **Strings in computed properties:** Wrap in the computed function body using the script pattern.
|
|
145
|
+
- **Conditional content:** If a string appears inside a ternary or conditional, replace only the string literal, not the condition.
|
|
146
|
+
- **Multi-line strings:** Replace the entire multi-line string as one unit. Do not split across multiple patches.
|
|
@@ -0,0 +1,115 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: what-is-content
|
|
3
|
+
description: "Heuristics for identifying content vs code strings in source files"
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
# What Is Content
|
|
7
|
+
|
|
8
|
+
Use these heuristics to identify content strings in source code. Content is user-visible text that should be managed separately from code. Accurate classification is critical -- extracting code strings as content breaks the application, while missing content strings leaves hardcoded text unmanageable.
|
|
9
|
+
|
|
10
|
+
---
|
|
11
|
+
|
|
12
|
+
## Extract These
|
|
13
|
+
|
|
14
|
+
These string types are user-visible content and should be extracted:
|
|
15
|
+
|
|
16
|
+
- **Headings and titles** in templates/JSX (`<h1>`, `<h2>`, etc.)
|
|
17
|
+
- **Paragraph text** and body copy
|
|
18
|
+
- **Button labels** (`<button>Submit</button>`)
|
|
19
|
+
- **Link text** (`<a>Learn more</a>`)
|
|
20
|
+
- **Form labels and placeholders** (`<label>`, `placeholder="..."`)
|
|
21
|
+
- **Error messages** shown to users
|
|
22
|
+
- **Success/notification messages**
|
|
23
|
+
- **Alt text** on images (`alt="..."`)
|
|
24
|
+
- **ARIA labels** (`aria-label="..."`)
|
|
25
|
+
- **Meta descriptions and page titles** (`<title>`, `<meta name="description">`)
|
|
26
|
+
- **Navigation items** (menu labels, breadcrumbs)
|
|
27
|
+
- **Tooltip text**
|
|
28
|
+
- **Empty state messages** ("No results found")
|
|
29
|
+
- **CTA (call-to-action) text**
|
|
30
|
+
|
|
31
|
+
---
|
|
32
|
+
|
|
33
|
+
## Do NOT Extract These
|
|
34
|
+
|
|
35
|
+
These string types are code infrastructure and must remain in source:
|
|
36
|
+
|
|
37
|
+
- CSS class names, HTML IDs
|
|
38
|
+
- Variable names, function names, parameter names
|
|
39
|
+
- Technical identifiers (API endpoints, route paths, event names)
|
|
40
|
+
- Import paths and file paths
|
|
41
|
+
- Numbers used as constants (port numbers, HTTP status codes, pixel dimensions, timeouts)
|
|
42
|
+
- Strings shorter than 3 characters (unless semantically meaningful: "OK", "No", "Yes")
|
|
43
|
+
- Regular expressions
|
|
44
|
+
- Configuration values (environment variables, feature flags)
|
|
45
|
+
- Log messages (internal, not user-facing)
|
|
46
|
+
- Code comments
|
|
47
|
+
- Test assertion strings
|
|
48
|
+
- JSON keys
|
|
49
|
+
- Enum values used as code identifiers (not displayed to users)
|
|
50
|
+
|
|
51
|
+
---
|
|
52
|
+
|
|
53
|
+
## Dictionary Interpolation Limitation
|
|
54
|
+
|
|
55
|
+
Strings containing dynamic expressions (`${variable}`, template literals with runtime values) cannot be stored in dictionaries. These must remain as hardcoded expressions in source code.
|
|
56
|
+
|
|
57
|
+
### CANNOT Extract
|
|
58
|
+
|
|
59
|
+
- `"Add a new entry to ${modelId}"` -- contains runtime variable
|
|
60
|
+
- `` `Hello, ${user.name}!` `` -- template literal with variable
|
|
61
|
+
|
|
62
|
+
### CAN Extract
|
|
63
|
+
|
|
64
|
+
- `"Add a new entry"` -- static string (parameterize separately if needed)
|
|
65
|
+
- `"Hello, World!"` -- no runtime variables
|
|
66
|
+
|
|
67
|
+
### Handling Parameterized Strings
|
|
68
|
+
|
|
69
|
+
Split parameterized strings: extract the static template pattern and leave the interpolation in code.
|
|
70
|
+
|
|
71
|
+
```
|
|
72
|
+
// Instead of: `"Add a new entry to ${modelId}"`
|
|
73
|
+
// Extract: dictionary key "add-entry-to" = "Add a new entry to"
|
|
74
|
+
// Code: `${t['add-entry-to']} ${modelId}`
|
|
75
|
+
```
|
|
76
|
+
|
|
77
|
+
For i18n frameworks that support interpolation (e.g., `{count} items`), use the framework's built-in interpolation syntax instead of string concatenation. Check the project's i18n library documentation.
|
|
78
|
+
|
|
79
|
+
---
|
|
80
|
+
|
|
81
|
+
## Edge Cases
|
|
82
|
+
|
|
83
|
+
| String | Extract? | Reason |
|
|
84
|
+
|--------|----------|--------|
|
|
85
|
+
| `"OK"` | Yes | User-visible confirmation |
|
|
86
|
+
| `"px"` | No | CSS unit, not content |
|
|
87
|
+
| `"Loading..."` | Yes | User-visible state message |
|
|
88
|
+
| `"GET"` | No | HTTP method, technical |
|
|
89
|
+
| `"en"` | No | Locale code, technical |
|
|
90
|
+
| `"Submit Form"` | Yes | User-visible button label |
|
|
91
|
+
| `"/api/users"` | No | API endpoint |
|
|
92
|
+
| `"flex"` | No | CSS value |
|
|
93
|
+
| `"user.name"` | No | Object property path |
|
|
94
|
+
| `"An error occurred"` | Yes | User-facing error message |
|
|
95
|
+
| `"true"` / `"false"` | No | Boolean string, technical |
|
|
96
|
+
| `"none"` | No | CSS/config value |
|
|
97
|
+
| `"No results found"` | Yes | User-visible empty state |
|
|
98
|
+
| `"application/json"` | No | MIME type, technical |
|
|
99
|
+
| `"Welcome back!"` | Yes | User-visible greeting |
|
|
100
|
+
| `"div"` | No | HTML tag name, technical |
|
|
101
|
+
| `"default"` | No | Config/switch value, technical |
|
|
102
|
+
|
|
103
|
+
---
|
|
104
|
+
|
|
105
|
+
## Decision Framework
|
|
106
|
+
|
|
107
|
+
When uncertain whether a string is content, ask these questions in order:
|
|
108
|
+
|
|
109
|
+
1. **Is this string displayed to the end user?** If no, do not extract.
|
|
110
|
+
2. **Would this string need to change for a different locale?** If yes, extract.
|
|
111
|
+
3. **Would a content editor reasonably want to update this text?** If yes, extract.
|
|
112
|
+
4. **Does this string serve a technical function (routing, config, API)?** If yes, do not extract.
|
|
113
|
+
5. **Is this string used as an identifier in code logic?** If yes, do not extract.
|
|
114
|
+
|
|
115
|
+
If the answer is still unclear after these questions, err on the side of NOT extracting. It is safer to leave a string hardcoded than to extract a technical identifier.
|
|
@@ -0,0 +1,180 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: contentrain-quality
|
|
3
|
+
description: "Content quality, SEO, accessibility, and media rules for Contentrain projects. Use when reviewing content quality, checking SEO, or validating media assets."
|
|
4
|
+
metadata:
|
|
5
|
+
author: Contentrain
|
|
6
|
+
version: "1.0.0"
|
|
7
|
+
---
|
|
8
|
+
|
|
9
|
+
# Contentrain Quality
|
|
10
|
+
|
|
11
|
+
Quality rules govern how AI agents create, edit, and review content in Contentrain projects. These rules ensure every piece of content meets standards for writing quality, SEO, accessibility, and media optimization before publication.
|
|
12
|
+
|
|
13
|
+
---
|
|
14
|
+
|
|
15
|
+
## Quick Checklist
|
|
16
|
+
|
|
17
|
+
Before committing any content, verify all of the following:
|
|
18
|
+
|
|
19
|
+
- [ ] Heading hierarchy is sequential (H1 -> H2 -> H3, no skips)
|
|
20
|
+
- [ ] Exactly one H1 per page
|
|
21
|
+
- [ ] Title is 50-60 characters, primary keyword within first 30 chars, unique
|
|
22
|
+
- [ ] Description is 120-160 characters, complete sentence, unique, differs from title
|
|
23
|
+
- [ ] No placeholder text anywhere (no Lorem ipsum, TODO, TBD, [insert here])
|
|
24
|
+
- [ ] No duplicate content across entries in the same collection
|
|
25
|
+
- [ ] Tone matches `context.json > conventions.tone` configuration
|
|
26
|
+
- [ ] All terms match `vocabulary.json` (canonical terms, brand terms, no forbidden terms)
|
|
27
|
+
- [ ] All field constraints (length, pattern, required, unique) are satisfied
|
|
28
|
+
- [ ] Content follows the correct content type pattern (blog, landing page, docs, etc.)
|
|
29
|
+
- [ ] System fields (`id`, `createdAt`, `updatedAt`, `status`, `order`) are not in write payload
|
|
30
|
+
- [ ] Slug is lowercase, hyphenated, 3-5 words, no special characters
|
|
31
|
+
- [ ] All images have descriptive alt text (max 125 chars) or empty string for decorative
|
|
32
|
+
- [ ] OG/social fields populated if model supports them
|
|
33
|
+
- [ ] Media files follow naming conventions and size limits
|
|
34
|
+
|
|
35
|
+
---
|
|
36
|
+
|
|
37
|
+
## Key Writing Rules
|
|
38
|
+
|
|
39
|
+
### Heading Hierarchy
|
|
40
|
+
|
|
41
|
+
- Exactly ONE H1 per page (the page/entry title).
|
|
42
|
+
- Follow sequential order: H1 -> H2 -> H3 -> H4. Never skip levels.
|
|
43
|
+
- Every heading must have content beneath it. No empty sections.
|
|
44
|
+
|
|
45
|
+
### Titles
|
|
46
|
+
|
|
47
|
+
- Length: 50-60 characters. Measure before committing.
|
|
48
|
+
- Place the primary keyword within the first 30 characters.
|
|
49
|
+
- Use action-oriented phrasing: "Build a REST API" not "REST API Overview".
|
|
50
|
+
- No clickbait, no ALL CAPS. Every title must be unique within its collection.
|
|
51
|
+
|
|
52
|
+
### Descriptions and Excerpts
|
|
53
|
+
|
|
54
|
+
- Length: 120-160 characters. Must be a complete sentence ending with a period.
|
|
55
|
+
- Summarize the VALUE the reader gets, not the process of writing.
|
|
56
|
+
- Must differ from the title.
|
|
57
|
+
|
|
58
|
+
### Body Content
|
|
59
|
+
|
|
60
|
+
- Active voice preferred. Target reading grade 8-10.
|
|
61
|
+
- Paragraphs: 3-5 sentences each. One idea per paragraph.
|
|
62
|
+
- Front-load key information in each paragraph.
|
|
63
|
+
|
|
64
|
+
### Lists
|
|
65
|
+
|
|
66
|
+
- Parallel grammatical structure across all items.
|
|
67
|
+
- Ordered lists for sequential steps, unordered for non-sequential items.
|
|
68
|
+
- Minimum 2 items in any list.
|
|
69
|
+
|
|
70
|
+
### Prohibitions
|
|
71
|
+
|
|
72
|
+
- No placeholder text: Lorem ipsum, TODO, TBD, [insert here], empty required fields.
|
|
73
|
+
- No duplicate content: titles, descriptions, and text blocks must be unique within a collection.
|
|
74
|
+
|
|
75
|
+
---
|
|
76
|
+
|
|
77
|
+
## Tone Configuration
|
|
78
|
+
|
|
79
|
+
### Reading Context
|
|
80
|
+
|
|
81
|
+
Before writing any content, check these files:
|
|
82
|
+
|
|
83
|
+
1. **`.contentrain/context.json`** -- Read `conventions.tone` and match the specified tone exactly. If absent, default to neutral professional.
|
|
84
|
+
2. **`.contentrain/vocabulary.json`** -- Use canonical terms exactly as defined. Respect `brand_terms` casing. Never use `forbidden_terms`. If vocabulary does not exist, maintain internal consistency.
|
|
85
|
+
|
|
86
|
+
### Content Type Voice Mapping
|
|
87
|
+
|
|
88
|
+
| Content Type | Voice | Characteristics |
|
|
89
|
+
|---|---|---|
|
|
90
|
+
| Marketing / Landing pages | Persuasive | Benefit-focused, confident, action-oriented, second person ("you") |
|
|
91
|
+
| Documentation / Guides | Instructional | Step-by-step, precise, imperative mood ("Run the command") |
|
|
92
|
+
| Error messages | Empathetic | Solution-oriented, no blame, explain what happened and what to do |
|
|
93
|
+
| UI labels | Concise | Consistent terminology, sentence case, no articles unless needed |
|
|
94
|
+
| Blog posts | Conversational-professional | Engaging, informative, first person plural ("we") acceptable |
|
|
95
|
+
| Changelogs | Factual | Past tense, specific, version-referenced, no marketing language |
|
|
96
|
+
|
|
97
|
+
### Consistency
|
|
98
|
+
|
|
99
|
+
- Do not shift tone mid-entry.
|
|
100
|
+
- Do not mix second person ("you") and third person ("the user") within the same entry.
|
|
101
|
+
- Maintain consistent use of contractions per project tone.
|
|
102
|
+
|
|
103
|
+
---
|
|
104
|
+
|
|
105
|
+
## Content Lifecycle States
|
|
106
|
+
|
|
107
|
+
Content moves through four states with specific requirements at each:
|
|
108
|
+
|
|
109
|
+
### Draft
|
|
110
|
+
|
|
111
|
+
- Focus on completeness over polish.
|
|
112
|
+
- ALL required fields must have real values -- no placeholders.
|
|
113
|
+
- Flag any fields the agent could not confidently populate.
|
|
114
|
+
|
|
115
|
+
### Review
|
|
116
|
+
|
|
117
|
+
- Vocabulary compliance: every term matches `vocabulary.json`.
|
|
118
|
+
- Tone consistency: matches configured tone throughout.
|
|
119
|
+
- Factual accuracy: all claims and references are verifiable.
|
|
120
|
+
- Cross-locale coverage: all target locales have corresponding entries.
|
|
121
|
+
- Field constraint compliance: all values within defined bounds.
|
|
122
|
+
|
|
123
|
+
### Published
|
|
124
|
+
|
|
125
|
+
ALL of the following must be true:
|
|
126
|
+
|
|
127
|
+
- Every required field is populated with final content.
|
|
128
|
+
- Every supported locale has a complete translation.
|
|
129
|
+
- Zero placeholder text anywhere.
|
|
130
|
+
- All relation fields reference existing, published entries.
|
|
131
|
+
- SEO fields (title, description, slug) are populated and valid.
|
|
132
|
+
- Content passes tone and vocabulary checks.
|
|
133
|
+
|
|
134
|
+
### Archived
|
|
135
|
+
|
|
136
|
+
Move content to archived status when:
|
|
137
|
+
|
|
138
|
+
- It references deprecated features, APIs, or products.
|
|
139
|
+
- Dates or events referenced are more than 12 months past.
|
|
140
|
+
- The content has been superseded by a newer entry.
|
|
141
|
+
- Statistics or data points are outdated and cannot be updated.
|
|
142
|
+
|
|
143
|
+
Never delete content. Archive it. Deletion is a manual human decision.
|
|
144
|
+
|
|
145
|
+
---
|
|
146
|
+
|
|
147
|
+
## Content Type Patterns
|
|
148
|
+
|
|
149
|
+
### Blog Post (document kind)
|
|
150
|
+
|
|
151
|
+
Hook opening -> Context paragraph -> Scannable body with H2/H3 -> Conclusion -> CTA. Do not start with "In this article, we will discuss..."
|
|
152
|
+
|
|
153
|
+
### Landing Page (singleton kind)
|
|
154
|
+
|
|
155
|
+
Hero statement (max 10 words) -> Problem -> Solution -> Features (3-6) -> Social proof -> CTA.
|
|
156
|
+
|
|
157
|
+
### Documentation (document kind)
|
|
158
|
+
|
|
159
|
+
Prerequisites callout -> Overview -> Step-by-step instructions -> Expected result -> Troubleshooting (min 2 items).
|
|
160
|
+
|
|
161
|
+
### Error Messages (dictionary kind)
|
|
162
|
+
|
|
163
|
+
What happened -> Why -> How to fix. No blame language, no technical jargon, no naked error codes.
|
|
164
|
+
|
|
165
|
+
### Form Labels (dictionary kind)
|
|
166
|
+
|
|
167
|
+
Noun or noun phrase, sentence case, no trailing colons. Helper text: one sentence with format requirements.
|
|
168
|
+
|
|
169
|
+
### Navigation Items (dictionary kind)
|
|
170
|
+
|
|
171
|
+
1-2 words maximum. Verbs for actions, nouns for destinations. No abbreviations.
|
|
172
|
+
|
|
173
|
+
---
|
|
174
|
+
|
|
175
|
+
## References
|
|
176
|
+
|
|
177
|
+
- [Content Quality](references/content-quality.md) -- Detailed content writing rules, structure, tone, content type patterns, lifecycle
|
|
178
|
+
- [SEO Rules](references/seo.md) -- SEO optimization rules for content fields, meta tags, and structured data
|
|
179
|
+
- [Accessibility](references/accessibility.md) -- Accessibility rules for content: alt text, ARIA, color contrast, reading level
|
|
180
|
+
- [Media Rules](references/media.md) -- Media asset rules: image optimization, video handling, file naming conventions
|