@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.
Files changed (42) hide show
  1. package/README.md +65 -60
  2. package/dist/index.cjs +65 -1
  3. package/dist/index.d.cts +50 -2
  4. package/dist/index.d.cts.map +1 -1
  5. package/dist/index.d.mts +50 -2
  6. package/dist/index.d.mts.map +1 -1
  7. package/dist/index.mjs +65 -2
  8. package/dist/index.mjs.map +1 -1
  9. package/package.json +3 -1
  10. package/skills/contentrain/SKILL.md +149 -0
  11. package/skills/contentrain/references/architecture.md +212 -0
  12. package/skills/contentrain/references/content-formats.md +279 -0
  13. package/skills/contentrain/references/i18n.md +298 -0
  14. package/skills/contentrain/references/mcp-pipelines.md +195 -0
  15. package/skills/contentrain/references/mcp-tools.md +308 -0
  16. package/skills/contentrain/references/model-kinds.md +330 -0
  17. package/skills/contentrain/references/schema-types.md +177 -0
  18. package/skills/contentrain/references/security.md +146 -0
  19. package/skills/contentrain/references/workflow.md +285 -0
  20. package/skills/contentrain-bulk/SKILL.md +99 -0
  21. package/skills/contentrain-content/SKILL.md +162 -0
  22. package/skills/contentrain-diff/SKILL.md +62 -0
  23. package/skills/contentrain-doctor/SKILL.md +62 -0
  24. package/skills/contentrain-generate/SKILL.md +183 -0
  25. package/skills/contentrain-generate/references/generated-client.md +198 -0
  26. package/skills/contentrain-init/SKILL.md +99 -0
  27. package/skills/contentrain-model/SKILL.md +141 -0
  28. package/skills/contentrain-normalize/SKILL.md +185 -0
  29. package/skills/contentrain-normalize/references/extraction.md +164 -0
  30. package/skills/contentrain-normalize/references/reuse.md +146 -0
  31. package/skills/contentrain-normalize/references/what-is-content.md +115 -0
  32. package/skills/contentrain-quality/SKILL.md +180 -0
  33. package/skills/contentrain-quality/references/accessibility.md +160 -0
  34. package/skills/contentrain-quality/references/content-quality.md +299 -0
  35. package/skills/contentrain-quality/references/media.md +170 -0
  36. package/skills/contentrain-quality/references/seo.md +229 -0
  37. package/skills/contentrain-review/SKILL.md +170 -0
  38. package/skills/contentrain-sdk/SKILL.md +145 -0
  39. package/skills/contentrain-sdk/references/bundler-config.md +135 -0
  40. package/skills/contentrain-serve/SKILL.md +96 -0
  41. package/skills/contentrain-translate/SKILL.md +180 -0
  42. 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