Defines the 23 rules used by `/architect:validate-consistency` to detect conflicts across project outputs. These rules are the "law of the land" for cross-command consistency.
Scanned 5/27/2026
Install via CLI
openskills install navraj007in/architecture-cowork-plugin# Consistency Rules Skill
Defines the 23 rules used by `/architect:validate-consistency` to detect conflicts across project outputs. These rules are the "law of the land" for cross-command consistency.
## Rule Catalog
The 23 rules are divided into three categories:
1. **State Rules (6)** — Validate individual fields in `_state.json`
2. **Output Rules (8)** — Validate files generated by individual commands
3. **Cross-Command Rules (9)** — Detect conflicts between outputs of different commands
---
## State Rules (RULE-S-xxx)
Validate that fields in `_state.json` follow the canonical schema and contain sensible values.
### RULE-S-001: Design colors must be valid hex
**Rule:** Every field under `_state.json.design` that ends with a color name (primary, secondary, accent, surface, text_primary, text_secondary, etc.) must be a valid hex color code in format `#RRGGBB`.
**Check:**
```javascript
/^#[0-9a-fA-F]{6}$/.test(design_field)
```
**Examples:**
- ✅ `#f97316` — valid
- ✅ `#0EA5E9` — valid (case-insensitive)
- ❌ `f97316` — missing #
- ❌ `#f973` — too short
- ❌ `rgb(249, 115, 22)` — wrong format
**Severity:** Critical (breaks design token generation)
**Auto-fixable:** Yes (add # prefix or reject invalid)
---
### RULE-S-002: Component ports must be unique and numeric
**Rule:** Every component in `_state.json.components[]` must have a unique port number. Ports must be integers in range 1024-65535. No two components can share the same port.
**Check:**
```javascript
components.forEach(c => {
if (typeof c.port !== 'number') throw "PORT_NOT_NUMBER";
if (c.port < 1024 || c.port > 65535) throw "PORT_OUT_OF_RANGE";
});
if (new Set(components.map(c => c.port)).size !== components.length) {
throw "DUPLICATE_PORT";
}
```
**Examples:**
- ✅ Components: api-server (3000), web-app (3001), worker (5000) — all unique, all valid
- ❌ Two components on port 3000 — duplicate
- ❌ Component port "3000" (string) — not numeric
- ❌ Component port 999 — below 1024
**Severity:** Critical (port collisions block local dev)
**Auto-fixable:** Partial (can detect, requires user to reassign)
---
### RULE-S-003: Entity names must match schema if schema exists
**Rule:** Every entity name in `_state.json.entities[]` must have a corresponding type definition in the data model schema. If schema exists (schema.prisma, etc.), all entities must be defined there.
**Check:**
```javascript
const stateEntityNames = state.entities.map(e => e.name);
const schemaEntityNames = parseSchema(schema).types.map(t => t.name);
const missing = stateEntityNames.filter(n => !schemaEntityNames.includes(n));
if (missing.length > 0) throw "ENTITY_NOT_IN_SCHEMA: " + missing;
```
**Examples:**
- ✅ State has User, Product; schema has User, Product — match
- ❌ State has User, Product, Order; schema has User, Product — Order missing from schema
- ✅ No schema exists — skip this rule
**Severity:** Warning (incomplete schema, future code gen problems)
**Auto-fixable:** No (requires investigation: add entity to schema or remove from state)
---
### RULE-S-004: Tech stack versions must be valid semver
**Rule:** All version strings in `_state.json.tech_stack` (e.g., "Node.js 18", "Next.js 14", "PostgreSQL 15") must parse as valid semantic versioning (MAJOR.MINOR.PATCH) or be underspecified (MAJOR or MAJOR.MINOR).
**Check:**
```javascript
const versionRegex = /^(\d+)(?:\.(\d+))?(?:\.(\d+))?(?:-[a-zA-Z0-9]+)?$/;
tech_stack_values.forEach(v => {
if (!versionRegex.test(v)) throw "INVALID_VERSION: " + v;
});
```
**Examples:**
- ✅ "Node.js 18" (parsed as 18.0.0)
- ✅ "Node.js 18.2" (parsed as 18.2.0)
- ✅ "Node.js 18.2.1" (exact semver)
- ❌ "Node.js v18" — 'v' prefix not allowed
- ❌ "Node.js latest" — not a version
- ❌ "Node.js 18.2.1.5" — too many parts
**Severity:** Warning (doesn't break things immediately, but version pinning issues later)
**Auto-fixable:** Partial (can normalize "v18" → "18")
---
### RULE-S-005: Persona and decision IDs must be unique
**Rule:** Every ID in `_state.json.personas[]` and `_state.json.decisions[]` must be unique within its array. IDs are user-facing keys for tracking decisions and personas.
**Check:**
```javascript
const personaIds = state.personas.map(p => p.id);
if (new Set(personaIds).size !== personaIds.length) throw "DUPLICATE_PERSONA_ID";
const decisionIds = state.decisions.map(d => d.id);
if (new Set(decisionIds).size !== decisionIds.length) throw "DUPLICATE_DECISION_ID";
```
**Examples:**
- ✅ Personas: P-001, P-002, P-003 — all unique
- ❌ Personas: P-001, P-001 — duplicate ID
- ✅ Decisions: D-001, D-002, D-003 — all unique
- ❌ Decisions: D-001, D-002, D-001 — duplicate ID
**Severity:** Critical (breaks persona/decision tracking)
**Auto-fixable:** No (requires renumbering, which affects references)
---
### RULE-S-006: All referenced fields must exist
**Rule:** If one field in `_state.json` references another field (e.g., an entity references a field name), that reference must resolve. Common referential integrity checks:
- `entities[].fields[]` must be strings (field names)
- `risk_register[].mitigations[]` must reference actual mitigation strategies
- `personas[].pain_points[]` must be defined (no null/undefined)
**Check:**
```javascript
entities.forEach(entity => {
entity.fields.forEach(field => {
if (typeof field !== 'string' || !field) throw "INVALID_FIELD_NAME: " + entity.name;
});
});
// Example: if personas reference skills, verify skills exist
personas.forEach(p => {
p.skills?.forEach(skill => {
if (!availableSkills.includes(skill)) throw "SKILL_NOT_FOUND: " + skill;
});
});
```
**Examples:**
- ✅ Entity User has fields: [id, email, name, role] — all strings
- ❌ Entity User has fields: [id, 123, name] — 123 is not a string
- ✅ Persona references skill "procurement" and that skill is defined
- ❌ Persona references skill "unknown_skill" which doesn't exist
**Severity:** Warning (broken references cause issues downstream)
**Auto-fixable:** Partial (can remove broken references with warning)
---
## Output Rules (RULE-O-xxx)
Validate that individual command outputs are internally consistent and match expectations.
### RULE-O-001: Design tokens file must match _state.json colors
**Rule:** The `design-system/design-tokens.json` file must have the same color values as `_state.json.design`. These should be identical (no rounding, no color space conversion).
**Check:**
```javascript
const state_colors = {
primary: state.design.primary,
secondary: state.design.secondary,
// ... all colors
};
const token_colors = tokens_json.colors;
if (JSON.stringify(state_colors) !== JSON.stringify(token_colors)) {
throw "COLOR_MISMATCH";
}
```
**Examples:**
- ✅ _state.json.design.primary = "#f97316", tokens_json.primary = "#f97316" — match
- ❌ _state.json.design.primary = "#f97316", tokens_json.primary = "#0ea5e9" — mismatch
- ❌ _state.json.design.primary = "#f97316", tokens_json.primary = "#F97316" (case diff) — treated as mismatch (case-sensitive)
**Severity:** Critical (UI color inconsistency)
**Auto-fixable:** Partial (can prompt user which source is correct)
**Root cause:** Usually design-system command ran with old state, or state was updated after design-system ran.
---
### RULE-O-002: Scaffold files must match component definitions
**Rule:** Every component defined in `_state.json.components[]` must have corresponding files in the scaffold directory. Component name must match folder/file naming (kebab-case).
**Check:**
```javascript
const state_components = state.components.map(c => kebabCase(c.name));
const scaffold_dirs = fs.readdirSync('src/components').filter(d => fs.statSync(...).isDirectory());
const missing = state_components.filter(c => !scaffold_dirs.includes(c));
if (missing.length > 0) throw "COMPONENT_NOT_SCAFFOLDED: " + missing;
```
**Examples:**
- ✅ State has component "api-server", scaffold has `src/services/api-server/` directory
- ❌ State has component "AuthService", scaffold has no `auth-service/` directory (missing)
- ❌ State has component "web-app", but scaffold has `web_app/` (wrong naming)
**Severity:** Warning (incomplete scaffold, inconsistent naming)
**Auto-fixable:** Partial (can detect, requires investigation)
---
### RULE-O-003: Cost estimate must be based on current component count
**Rule:** The cost estimate (if generated) must reference the current component count from `_state.json`. If state has N components but cost-estimate was run with M components (where M ≠ N), flag as stale.
**Check:**
```javascript
const state_component_count = state.components.length;
const cost_estimate_component_count = cost_estimate.metadata.component_count;
const age_days = daysSince(cost_estimate.generated_at);
if (state_component_count !== cost_estimate_component_count || age_days > 14) {
throw "COST_ESTIMATE_STALE";
}
```
**Examples:**
- ✅ State has 8 components, cost-estimate.metadata.component_count = 8, generated 2 days ago — fresh
- ❌ State has 12 components, cost-estimate.metadata.component_count = 8 — undercounted by 33%
- ❌ cost-estimate generated 30 days ago — stale regardless of match
**Severity:** Warning (estimates outdated, budget planning impacts)
**Auto-fixable:** No (requires rerunning /architect:cost-estimate)
---
### RULE-O-004: Test coverage percentage must be 0-100%
**Rule:** Any field in test outputs that reports coverage (test_suite.coverage%, test_suite.coverage_target%) must be a number between 0 and 100. No negative percentages, no >100%.
**Check:**
```javascript
if (typeof coverage !== 'number' || coverage < 0 || coverage > 100) {
throw "INVALID_COVERAGE_PERCENT: " + coverage;
}
```
**Examples:**
- ✅ coverage = 75 — valid
- ✅ coverage = 0 — valid (no tests yet)
- ✅ coverage = 100 — valid (perfect)
- ❌ coverage = 120 — impossible
- ❌ coverage = -10 — impossible
**Severity:** Critical (invalid metrics break dashboards)
**Auto-fixable:** Yes (clamp to 0-100)
---
### RULE-O-005: Compliance rules must reference existing entities or patterns
**Rule:** Each compliance control in a compliance report must reference real entities (from `_state.json.entities[]`) or architectural patterns that actually exist in the scaffold.
**Check:**
```javascript
const state_entities = state.entities.map(e => e.name);
compliance.controls.forEach(control => {
if (control.applies_to_entity && !state_entities.includes(control.applies_to_entity)) {
throw "ENTITY_NOT_FOUND: " + control.applies_to_entity;
}
});
```
**Examples:**
- ✅ Compliance rule: "Data encryption for User entity" → User exists in _state.json
- ❌ Compliance rule: "Data encryption for UnknownEntity" → UnknownEntity doesn't exist
**Severity:** Warning (orphaned controls, incomplete scope)
**Auto-fixable:** No (requires investigation)
---
### RULE-O-006: Monitoring metrics must align with tech stack
**Rule:** Monitoring outputs (dashboards, metrics, alerts) must reference services/frameworks that are actually in the tech stack. Can't monitor "Kafka" if tech stack doesn't include Kafka.
**Check:**
```javascript
const tech_stack_items = state.tech_stack.backend.concat(state.tech_stack.integrations);
monitoring.metrics.forEach(metric => {
if (!tech_stack_items.some(t => metric.service.includes(t))) {
throw "SERVICE_NOT_IN_TECH_STACK: " + metric.service;
}
});
```
**Examples:**
- ✅ Tech stack has "Datadog" in integrations, monitoring references Datadog metrics — match
- ❌ Tech stack has "CloudWatch", monitoring references Datadog (wrong provider)
- ❌ Monitoring references "Kafka", but tech stack has "RabbitMQ"
**Severity:** Warning (monitoring won't work, missing instrumentation)
**Auto-fixable:** No (requires choosing a monitoring provider)
---
### RULE-O-007: Load test scenarios must use valid endpoints
**Rule:** Each scenario in a load test plan must reference endpoints that actually exist in the project (from API contracts or scaffold routes).
**Check:**
```javascript
const valid_endpoints = contracts.api.paths;
load_test.scenarios.forEach(scenario => {
scenario.requests.forEach(req => {
if (!valid_endpoints.includes(req.endpoint)) {
throw "ENDPOINT_NOT_FOUND: " + req.endpoint;
}
});
});
```
**Examples:**
- ✅ Load test scenario requests `POST /api/users` and that endpoint exists in scaffold
- ❌ Load test scenario requests `GET /unknown/endpoint` — endpoint doesn't exist
**Severity:** Warning (load test won't run correctly)
**Auto-fixable:** No (requires investigation)
---
### RULE-O-008: Documentation must reference existing components
**Rule:** All generated documentation (API docs, guides, runbooks) must reference components, entities, or patterns that actually exist in the project.
**Check:**
```javascript
const state_components = state.components.map(c => c.name);
docs.content.split('\n').forEach(line => {
const referenced_components = extractComponentReferences(line);
referenced_components.forEach(comp => {
if (!state_components.includes(comp)) {
throw "COMPONENT_NOT_FOUND_IN_DOCS: " + comp;
}
});
});
```
**Examples:**
- ✅ Docs reference "api-server" and that component exists in _state.json
- ❌ Docs reference "legacy-service" which no longer exists
**Severity:** Info (documentation outdated, confusing but not blocking)
**Auto-fixable:** No (requires updating docs)
---
## Cross-Command Rules (RULE-X-xxx)
Detect conflicts between outputs of different commands. These are the most important rules for catching architectural drift.
### RULE-X-001: No component should exist in both created and deleted lists
**Rule:** If a component was created by `/architect:scaffold-component`, it shouldn't appear in any "removed components" or "deprecated" list. A component can't exist and not exist.
**Check:**
```javascript
const created = activity.filter(a => a.phase === 'scaffold-component').map(a => a.component);
const removed = state.deprecated_components || [];
const both = created.filter(c => removed.includes(c));
if (both.length > 0) throw "COMPONENT_IN_BOTH_CREATED_AND_REMOVED: " + both;
```
**Examples:**
- ✅ Scaffold created auth-service, remove list is empty — consistent
- ❌ Scaffold created auth-service, remove list contains auth-service — contradiction
**Severity:** Critical (architectural confusion)
**Auto-fixable:** No (requires deciding to keep or remove component)
---
### RULE-X-002: Design personality must be consistent across scaffold and design-system
**Rule:** The design personality chosen in `_state.json.design.personality` must be used consistently across the scaffold. If state says "bold-commercial", scaffold component names and structure should reflect that personality (not "serene-health" or other personality).
**Check:**
```javascript
const personality = state.design.personality;
const scaffold_personalities = extractPersonalitiesFromScaffold();
if (scaffold_personalities.length > 0 && !scaffold_personalities.includes(personality)) {
throw "PERSONALITY_MISMATCH: " + personality + " vs " + scaffold_personalities;
}
```
**Examples:**
- ✅ State personality = "bold-commercial", scaffold component naming is bold/strong — match
- ❌ State personality = "serene-health", scaffold has commercial/aggressive styling — mismatch
**Severity:** Warning (visual inconsistency, brand confusion)
**Auto-fixable:** No (requires aligning design direction)
---
### RULE-X-003: Entity count must increase or stay same, never decrease
**Rule:** As the project evolves, the entity count in `_state.json.entities[]` should only increase or stay the same. It should never decrease (would indicate entities were deleted, which is a major architecture change that should be audited).
**Check:**
```javascript
const current_count = state.entities.length;
const previous_count = previousState.entities.length;
if (current_count < previous_count) {
throw "ENTITY_COUNT_DECREASED: " + previous_count + " → " + current_count;
}
```
**Examples:**
- ✅ Entity count: 5 → 8 (grew, normal)
- ✅ Entity count: 8 → 8 (same, normal)
- ❌ Entity count: 8 → 5 (decreased, needs audit)
**Severity:** Critical (indicates major refactor that should be tracked)
**Auto-fixable:** No (requires investigation)
---
### RULE-X-004: Blueprint architecture must match scaffold service layout
**Rule:** The services/components described in the blueprint must match the actual scaffolded components. If blueprint says "api-server, web-app, worker", scaffold must have those 3 components (not 2 or 4).
**Check:**
```javascript
const blueprint_services = blueprint.services.map(s => s.name);
const scaffold_services = state.components.map(c => c.name);
if (JSON.stringify(blueprint_services.sort()) !== JSON.stringify(scaffold_services.sort())) {
throw "BLUEPRINT_SCAFFOLD_MISMATCH";
}
```
**Examples:**
- ✅ Blueprint describes api-server, web-app, worker; scaffold has all 3
- ❌ Blueprint describes api-server, web-app, worker; scaffold only has api-server and web-app (missing worker)
**Severity:** Warning (architectural mismatch, confusing for developers)
**Auto-fixable:** No (requires deciding which is correct)
---
### RULE-X-005: Tech stack languages must match codebase
**Rule:** Languages listed in `_state.json.tech_stack.backend` and `.frontend` must match the actual programming languages found in the scaffolded source code.
**Check:**
```javascript
const tech_languages = extractLanguages(state.tech_stack);
const codebase_languages = detectLanguagesInScaffold();
const missing_in_code = tech_languages.filter(l => !codebase_languages.includes(l));
const extra_in_code = codebase_languages.filter(l => !tech_languages.includes(l));
if (missing_in_code.length > 0 || extra_in_code.length > 0) {
throw "LANGUAGE_MISMATCH: " + missing_in_code + " missing, " + extra_in_code + " extra";
}
```
**Examples:**
- ✅ Tech stack says "Node.js", codebase has .js/.ts files — match
- ✅ Tech stack says "Python + Node.js", codebase has both .py and .js/.ts — match
- ❌ Tech stack says "Python", codebase has only .js files — mismatch
- ❌ Tech stack says "Go", codebase has .java files — mismatch
**Severity:** Warning (tech stack and codebase are out of sync)
**Auto-fixable:** No (requires investigation)
---
### RULE-X-006: Monitoring provider must be listed in tech stack integrations
**Rule:** The observability provider chosen in monitoring setup (Datadog, New Relic, Prometheus, etc.) must be listed in `_state.json.tech_stack.integrations[]`.
**Check:**
```javascript
const monitoring_provider = monitoring.provider; // e.g., "Datadog"
const integrations = state.tech_stack.integrations;
if (!integrations.includes(monitoring_provider)) {
throw "PROVIDER_NOT_IN_INTEGRATIONS: " + monitoring_provider;
}
```
**Examples:**
- ✅ Monitoring setup uses Datadog, tech_stack.integrations includes "Datadog" — match
- ❌ Monitoring setup uses Datadog, tech_stack.integrations doesn't list Datadog — mismatch
**Severity:** Medium (missing from tech stack list, but monitoring will still work)
**Auto-fixable:** Yes (add provider to integrations)
---
### RULE-X-007: Compliance frameworks must be supported by tech stack
**Rule:** Each compliance framework in the compliance plan (GDPR, SOC 2, HIPAA, etc.) must be achievable with the chosen tech stack. Some tech stacks can't support certain compliance frameworks (e.g., free tier services can't do HIPAA).
**Check:**
```javascript
const compliance_frameworks = compliance.frameworks;
const tech_stack = state.tech_stack;
compliance_frameworks.forEach(fw => {
const supported = isSupportedBy(fw, tech_stack);
if (!supported) throw "FRAMEWORK_NOT_SUPPORTED: " + fw + " by " + tech_stack;
});
```
**Examples:**
- ✅ Framework = SOC 2, tech_stack has enterprise services (AWS, Datadog) — achievable
- ❌ Framework = HIPAA, tech_stack uses free-tier only services — not achievable
- ❌ Framework = GDPR + data residency in EU, tech_stack only has US deployments — not achievable
**Severity:** Critical (compliance impossible with current tech stack)
**Auto-fixable:** No (requires changing tech stack or compliance requirements)
---
### RULE-X-008: Load test target RPS must be realistic for tech stack
**Rule:** The target requests-per-second (RPS) in load testing must be achievable with the chosen tech stack. Don't target 10k RPS if using a single-threaded framework.
**Check:**
```javascript
const target_rps = load_test.target_rps;
const tech_stack = state.tech_stack;
const achievable_rps = estimateRpsFor(tech_stack);
if (target_rps > achievable_rps * 1.5) {
// Allow 50% headroom for optimization
throw "TARGET_RPS_UNREALISTIC: " + target_rps + " vs achievable " + achievable_rps;
}
```
**Examples:**
- ✅ Target RPS = 1000, tech_stack is Node.js + Postgres on standard infra → achievable
- ❌ Target RPS = 100k, tech_stack is single-threaded Python → unrealistic
**Severity:** Warning (load test goals are unachievable)
**Auto-fixable:** No (requires adjusting tech stack or RPS targets)
---
### RULE-X-009: All referenced external services must exist in tech stack
**Rule:** Any external service referenced in blueprints, architecture diagrams, or data flow (Stripe, Auth0, SendGrid, etc.) must be listed in `_state.json.tech_stack.integrations[]`.
**Check:**
```javascript
const referenced_services = extractExternalServices(blueprint, architecture_diagram, dataflow);
const integrations = state.tech_stack.integrations;
const missing = referenced_services.filter(s => !integrations.includes(s));
if (missing.length > 0) throw "SERVICE_NOT_IN_INTEGRATIONS: " + missing;
```
**Examples:**
- ✅ Blueprint mentions Stripe for payments, tech_stack.integrations includes "Stripe" — match
- ❌ Blueprint mentions Auth0, tech_stack.integrations is empty or doesn't list Auth0 — missing
**Severity:** Medium (integration incomplete, but not blocking)
**Auto-fixable:** Yes (add to integrations)
---
## Using These Rules
### In `/architect:validate-consistency`
```pseudo
for each rule in [RULE-S-001 through RULE-X-009]:
result = apply_rule(rule, state, outputs)
if result === FAIL:
conflict = {
rule_id: rule.id,
severity: rule.severity,
description: rule.description,
remediation: rule.auto_fixable ? "can fix" : "manual fix needed"
}
add conflict to report
```
### In error messages
When a user runs a command and hits a rule violation, show them:
1. Which rule failed (RULE-X-009)
2. What it means in plain English
3. How to fix it
4. Why it matters
**Example:**
```
❌ RULE-X-009 violation: "Stripe" is referenced in blueprint but not listed in tech stack integrations.
This means: Your blueprint includes Stripe for payments, but I can't find "Stripe" in the integrations list.
Fix: Add "Stripe" to _state.json.tech_stack.integrations
Why: Keeping integrations in sync helps with cost estimation, security scanning, and deployment configuration.
```
---
## Rule Maintenance
### When to add a new rule
Add a new rule when you find:
1. A conflict that happened to 3+ projects (not a one-off)
2. A conflict that's expensive to fix (worth preventing)
3. A conflict that's systematic (not human error)
**Process:**
1. Create rule with clear name, check logic, examples
2. Add to appropriate category (State/Output/Cross-Command)
3. Test on 2-3 projects to validate
4. Document severity and auto-fix capability
5. Add to consistency-rules/SKILL.md
### When to deprecate a rule
Deprecate (don't delete) a rule when:
1. It becomes irrelevant (technology changed)
2. A different rule subsumes it
3. It's too noisy (more false positives than real issues)
**Process:**
1. Mark as `DEPRECATED` in rule definition
2. Continue to report violations but with low severity
3. Document why deprecated and what replaces it
4. Keep rule in doc for historical reference
---
## Related Commands
- `/architect:validate-consistency` — applies all these rules
- `/architect:check-state` — validates state schema (overlaps with State rules)
- `/architect:next-steps` — considers consistency when recommending commands
- `/architect:production-readiness` — blocks launch if critical rules fail
No comments yet. Be the first to comment!