@soleri/core 9.14.0 → 9.15.0
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/dist/brain/brain.d.ts +9 -0
- package/dist/brain/brain.d.ts.map +1 -1
- package/dist/brain/brain.js +11 -1
- package/dist/brain/brain.js.map +1 -1
- package/dist/brain/intelligence.d.ts.map +1 -1
- package/dist/brain/intelligence.js +24 -0
- package/dist/brain/intelligence.js.map +1 -1
- package/dist/brain/types.d.ts +1 -0
- package/dist/brain/types.d.ts.map +1 -1
- package/dist/chat/chat-session.d.ts +6 -0
- package/dist/chat/chat-session.d.ts.map +1 -1
- package/dist/chat/chat-session.js +68 -17
- package/dist/chat/chat-session.js.map +1 -1
- package/dist/curator/curator.d.ts +6 -0
- package/dist/curator/curator.d.ts.map +1 -1
- package/dist/curator/curator.js +138 -0
- package/dist/curator/curator.js.map +1 -1
- package/dist/curator/types.d.ts +10 -0
- package/dist/curator/types.d.ts.map +1 -1
- package/dist/engine/bin/soleri-engine.js +0 -0
- package/dist/flows/types.d.ts +16 -16
- package/dist/index.d.ts +2 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +2 -0
- package/dist/index.js.map +1 -1
- package/dist/intake/content-classifier.d.ts +10 -4
- package/dist/intake/content-classifier.d.ts.map +1 -1
- package/dist/intake/content-classifier.js +19 -5
- package/dist/intake/content-classifier.js.map +1 -1
- package/dist/intake/text-ingester.d.ts +18 -0
- package/dist/intake/text-ingester.d.ts.map +1 -1
- package/dist/intake/text-ingester.js +37 -13
- package/dist/intake/text-ingester.js.map +1 -1
- package/dist/planning/planner.d.ts +3 -0
- package/dist/planning/planner.d.ts.map +1 -1
- package/dist/planning/planner.js +43 -4
- package/dist/planning/planner.js.map +1 -1
- package/dist/plugins/types.d.ts +2 -2
- package/dist/runtime/admin-setup-ops.d.ts.map +1 -1
- package/dist/runtime/admin-setup-ops.js +59 -20
- package/dist/runtime/admin-setup-ops.js.map +1 -1
- package/dist/runtime/facades/orchestrate-facade.d.ts.map +1 -1
- package/dist/runtime/facades/orchestrate-facade.js +28 -1
- package/dist/runtime/facades/orchestrate-facade.js.map +1 -1
- package/dist/runtime/runtime.d.ts.map +1 -1
- package/dist/runtime/runtime.js +16 -0
- package/dist/runtime/runtime.js.map +1 -1
- package/dist/runtime/types.d.ts +19 -0
- package/dist/runtime/types.d.ts.map +1 -1
- package/dist/skills/sync-skills.d.ts.map +1 -1
- package/dist/skills/sync-skills.js +9 -3
- package/dist/skills/sync-skills.js.map +1 -1
- package/dist/skills/validate-skills.d.ts +32 -0
- package/dist/skills/validate-skills.d.ts.map +1 -0
- package/dist/skills/validate-skills.js +396 -0
- package/dist/skills/validate-skills.js.map +1 -0
- package/dist/vault/default-canonical-tags.d.ts +15 -0
- package/dist/vault/default-canonical-tags.d.ts.map +1 -0
- package/dist/vault/default-canonical-tags.js +65 -0
- package/dist/vault/default-canonical-tags.js.map +1 -0
- package/dist/vault/tag-normalizer.d.ts +42 -0
- package/dist/vault/tag-normalizer.d.ts.map +1 -0
- package/dist/vault/tag-normalizer.js +157 -0
- package/dist/vault/tag-normalizer.js.map +1 -0
- package/package.json +6 -2
- package/src/__tests__/embeddings.test.ts +3 -3
- package/src/brain/brain.ts +25 -1
- package/src/brain/intelligence.ts +25 -0
- package/src/brain/types.ts +1 -0
- package/src/chat/chat-session.ts +75 -17
- package/src/chat/chat-transport.test.ts +31 -1
- package/src/curator/curator.ts +180 -0
- package/src/curator/types.ts +10 -0
- package/src/index.ts +7 -0
- package/src/intake/content-classifier.ts +22 -4
- package/src/intake/text-ingester.ts +61 -12
- package/src/planning/planner.test.ts +86 -90
- package/src/planning/planner.ts +48 -4
- package/src/runtime/admin-setup-ops.test.ts +44 -0
- package/src/runtime/admin-setup-ops.ts +59 -20
- package/src/runtime/facades/orchestrate-facade.ts +27 -1
- package/src/runtime/runtime.ts +18 -0
- package/src/runtime/types.ts +19 -0
- package/src/skills/sync-skills.ts +9 -3
- package/src/skills/validate-skills.test.ts +205 -0
- package/src/skills/validate-skills.ts +470 -0
- package/src/vault/default-canonical-tags.ts +64 -0
- package/src/vault/tag-normalizer.test.ts +214 -0
- package/src/vault/tag-normalizer.ts +188 -0
- package/dist/embeddings/index.d.ts +0 -5
- package/dist/embeddings/index.d.ts.map +0 -1
- package/dist/embeddings/index.js +0 -3
- package/dist/embeddings/index.js.map +0 -1
- package/dist/knowledge-packs/knowledge-packs/community/.gitkeep +0 -0
- package/dist/knowledge-packs/knowledge-packs/salvador/salvador-craft/soleri-pack.json +0 -10
- package/dist/knowledge-packs/knowledge-packs/salvador/salvador-craft/vault/accessibility.json +0 -53
- package/dist/knowledge-packs/knowledge-packs/salvador/salvador-craft/vault/design-tokens.json +0 -26
- package/dist/knowledge-packs/knowledge-packs/salvador/salvador-craft/vault/design.json +0 -33
- package/dist/knowledge-packs/knowledge-packs/salvador/salvador-craft/vault/styling.json +0 -44
- package/dist/knowledge-packs/knowledge-packs/salvador/salvador-craft/vault/ux-laws.json +0 -36
- package/dist/knowledge-packs/knowledge-packs/salvador/salvador-craft/vault/ux.json +0 -36
- package/dist/knowledge-packs/knowledge-packs/salvador/salvador-engineering/soleri-pack.json +0 -10
- package/dist/knowledge-packs/knowledge-packs/salvador/salvador-engineering/vault/architecture.json +0 -143
- package/dist/knowledge-packs/knowledge-packs/salvador/salvador-engineering/vault/commercial.json +0 -16
- package/dist/knowledge-packs/knowledge-packs/salvador/salvador-engineering/vault/communication.json +0 -33
- package/dist/knowledge-packs/knowledge-packs/salvador/salvador-engineering/vault/component.json +0 -16
- package/dist/knowledge-packs/knowledge-packs/salvador/salvador-engineering/vault/express.json +0 -34
- package/dist/knowledge-packs/knowledge-packs/salvador/salvador-engineering/vault/leadership.json +0 -33
- package/dist/knowledge-packs/knowledge-packs/salvador/salvador-engineering/vault/methodology.json +0 -33
- package/dist/knowledge-packs/knowledge-packs/salvador/salvador-engineering/vault/monorepo.json +0 -33
- package/dist/knowledge-packs/knowledge-packs/salvador/salvador-engineering/vault/other.json +0 -73
- package/dist/knowledge-packs/knowledge-packs/salvador/salvador-engineering/vault/performance.json +0 -35
- package/dist/knowledge-packs/knowledge-packs/salvador/salvador-engineering/vault/prisma.json +0 -33
- package/dist/knowledge-packs/knowledge-packs/salvador/salvador-engineering/vault/product-strategy.json +0 -42
- package/dist/knowledge-packs/knowledge-packs/salvador/salvador-engineering/vault/react.json +0 -47
- package/dist/knowledge-packs/knowledge-packs/salvador/salvador-engineering/vault/security.json +0 -34
- package/dist/knowledge-packs/knowledge-packs/salvador/salvador-engineering/vault/testing.json +0 -33
- package/dist/knowledge-packs/knowledge-packs/salvador/salvador-engineering/vault/tooling.json +0 -85
- package/dist/knowledge-packs/knowledge-packs/salvador/salvador-engineering/vault/typescript.json +0 -34
- package/dist/knowledge-packs/knowledge-packs/salvador/salvador-engineering/vault/workflow.json +0 -46
- package/dist/knowledge-packs/knowledge-packs/salvador/salvador-uipro/soleri-pack.json +0 -10
- package/dist/knowledge-packs/knowledge-packs/salvador/salvador-uipro/vault/design.json +0 -2589
- package/dist/knowledge-packs/knowledge-packs/starter/architecture/soleri-pack.json +0 -10
- package/dist/knowledge-packs/knowledge-packs/starter/architecture/vault/patterns.json +0 -137
- package/dist/knowledge-packs/knowledge-packs/starter/design/soleri-pack.json +0 -10
- package/dist/knowledge-packs/knowledge-packs/starter/design/vault/patterns.json +0 -137
- package/dist/knowledge-packs/knowledge-packs/starter/security/soleri-pack.json +0 -10
- package/dist/knowledge-packs/knowledge-packs/starter/security/vault/patterns.json +0 -137
- /package/dist/knowledge-packs/{knowledge-packs/starter → starter}/api-design/soleri-pack.json +0 -0
- /package/dist/knowledge-packs/{knowledge-packs/starter → starter}/api-design/vault/patterns.json +0 -0
- /package/dist/knowledge-packs/{knowledge-packs/starter → starter}/nodejs/soleri-pack.json +0 -0
- /package/dist/knowledge-packs/{knowledge-packs/starter → starter}/nodejs/vault/patterns.json +0 -0
- /package/dist/knowledge-packs/{knowledge-packs/starter → starter}/react/soleri-pack.json +0 -0
- /package/dist/knowledge-packs/{knowledge-packs/starter → starter}/react/vault/patterns.json +0 -0
- /package/dist/knowledge-packs/{knowledge-packs/starter → starter}/testing/soleri-pack.json +0 -0
- /package/dist/knowledge-packs/{knowledge-packs/starter → starter}/testing/vault/patterns.json +0 -0
- /package/dist/knowledge-packs/{knowledge-packs/starter → starter}/typescript/soleri-pack.json +0 -0
- /package/dist/knowledge-packs/{knowledge-packs/starter → starter}/typescript/vault/patterns.json +0 -0
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Default canonical tag taxonomy for Soleri agents.
|
|
3
|
+
*
|
|
4
|
+
* These tags represent the most common knowledge domains. When a vault is
|
|
5
|
+
* configured with tagConstraintMode 'suggest' or 'enforce', incoming tags
|
|
6
|
+
* are mapped to the nearest entry in this list via edit-distance matching.
|
|
7
|
+
*
|
|
8
|
+
* To use these defaults in your agent runtime config:
|
|
9
|
+
* import { DEFAULT_CANONICAL_TAGS } from '@soleri/core';
|
|
10
|
+
* // ...
|
|
11
|
+
* canonicalTags: DEFAULT_CANONICAL_TAGS,
|
|
12
|
+
* tagConstraintMode: 'suggest',
|
|
13
|
+
*/
|
|
14
|
+
export const DEFAULT_CANONICAL_TAGS: string[] = [
|
|
15
|
+
'architecture',
|
|
16
|
+
'typescript',
|
|
17
|
+
'react',
|
|
18
|
+
'testing',
|
|
19
|
+
'workflow',
|
|
20
|
+
'design-tokens',
|
|
21
|
+
'accessibility',
|
|
22
|
+
'performance',
|
|
23
|
+
'security',
|
|
24
|
+
'planning',
|
|
25
|
+
'soleri',
|
|
26
|
+
'vault',
|
|
27
|
+
'mcp',
|
|
28
|
+
'claude-code',
|
|
29
|
+
'ai',
|
|
30
|
+
'learning',
|
|
31
|
+
'gamification',
|
|
32
|
+
'education',
|
|
33
|
+
'adhd',
|
|
34
|
+
'routing',
|
|
35
|
+
'orchestration',
|
|
36
|
+
'skills',
|
|
37
|
+
'automation',
|
|
38
|
+
'git',
|
|
39
|
+
'database',
|
|
40
|
+
'api',
|
|
41
|
+
'authentication',
|
|
42
|
+
'subagent',
|
|
43
|
+
'design-system',
|
|
44
|
+
'component',
|
|
45
|
+
'frontend',
|
|
46
|
+
'backend',
|
|
47
|
+
'tooling',
|
|
48
|
+
'monorepo',
|
|
49
|
+
'refactoring',
|
|
50
|
+
'debugging',
|
|
51
|
+
'deployment',
|
|
52
|
+
'configuration',
|
|
53
|
+
'documentation',
|
|
54
|
+
'pattern',
|
|
55
|
+
'anti-pattern',
|
|
56
|
+
'principle',
|
|
57
|
+
'decision',
|
|
58
|
+
'migration',
|
|
59
|
+
'plugin',
|
|
60
|
+
'hook',
|
|
61
|
+
'schema',
|
|
62
|
+
'pipeline',
|
|
63
|
+
'ingestion',
|
|
64
|
+
];
|
|
@@ -0,0 +1,214 @@
|
|
|
1
|
+
import { describe, it, expect } from 'vitest';
|
|
2
|
+
import {
|
|
3
|
+
computeEditDistance,
|
|
4
|
+
normalizeTag,
|
|
5
|
+
normalizeTags,
|
|
6
|
+
isMetadataTag,
|
|
7
|
+
} from './tag-normalizer.js';
|
|
8
|
+
|
|
9
|
+
// ─── computeEditDistance ────────────────────────────────────────────────────
|
|
10
|
+
|
|
11
|
+
describe('computeEditDistance', () => {
|
|
12
|
+
it('returns 0 for identical strings', () => {
|
|
13
|
+
expect(computeEditDistance('workflow', 'workflow')).toBe(0);
|
|
14
|
+
});
|
|
15
|
+
|
|
16
|
+
it('returns length of b for empty a', () => {
|
|
17
|
+
expect(computeEditDistance('', 'abc')).toBe(3);
|
|
18
|
+
});
|
|
19
|
+
|
|
20
|
+
it('returns length of a for empty b', () => {
|
|
21
|
+
expect(computeEditDistance('abc', '')).toBe(3);
|
|
22
|
+
});
|
|
23
|
+
|
|
24
|
+
it('single insertion: workflow → workflows', () => {
|
|
25
|
+
expect(computeEditDistance('workflow', 'workflows')).toBe(1);
|
|
26
|
+
});
|
|
27
|
+
|
|
28
|
+
it('single deletion: testing → testin', () => {
|
|
29
|
+
expect(computeEditDistance('testing', 'testin')).toBe(1);
|
|
30
|
+
});
|
|
31
|
+
|
|
32
|
+
it('single substitution: arch → arcs', () => {
|
|
33
|
+
expect(computeEditDistance('arch', 'arcs')).toBe(1);
|
|
34
|
+
});
|
|
35
|
+
|
|
36
|
+
it("distance 1: architcture → architecture (single insertion - missing 'e')", () => {
|
|
37
|
+
expect(computeEditDistance('architcture', 'architecture')).toBe(1);
|
|
38
|
+
});
|
|
39
|
+
|
|
40
|
+
it('distance 2: archtecrure → architecture (two edits)', () => {
|
|
41
|
+
expect(computeEditDistance('archtecrure', 'architecture')).toBe(2);
|
|
42
|
+
});
|
|
43
|
+
|
|
44
|
+
it('large distance: typescript → javascript', () => {
|
|
45
|
+
expect(computeEditDistance('typescript', 'javascript')).toBeGreaterThan(3);
|
|
46
|
+
});
|
|
47
|
+
|
|
48
|
+
it('is symmetric', () => {
|
|
49
|
+
expect(computeEditDistance('foo', 'bar')).toBe(computeEditDistance('bar', 'foo'));
|
|
50
|
+
});
|
|
51
|
+
});
|
|
52
|
+
|
|
53
|
+
// ─── isMetadataTag ──────────────────────────────────────────────────────────
|
|
54
|
+
|
|
55
|
+
describe('isMetadataTag', () => {
|
|
56
|
+
it('returns true when tag matches a prefix', () => {
|
|
57
|
+
expect(isMetadataTag('source:article', ['source:'])).toBe(true);
|
|
58
|
+
});
|
|
59
|
+
|
|
60
|
+
it('returns true for exact prefix match', () => {
|
|
61
|
+
expect(isMetadataTag('source:ingested', ['source:'])).toBe(true);
|
|
62
|
+
});
|
|
63
|
+
|
|
64
|
+
it('returns false when no prefix matches', () => {
|
|
65
|
+
expect(isMetadataTag('typescript', ['source:'])).toBe(false);
|
|
66
|
+
});
|
|
67
|
+
|
|
68
|
+
it('returns false with empty prefix list', () => {
|
|
69
|
+
expect(isMetadataTag('source:article', [])).toBe(false);
|
|
70
|
+
});
|
|
71
|
+
|
|
72
|
+
it('supports multiple prefixes', () => {
|
|
73
|
+
expect(isMetadataTag('meta:foo', ['source:', 'meta:'])).toBe(true);
|
|
74
|
+
});
|
|
75
|
+
});
|
|
76
|
+
|
|
77
|
+
// ─── normalizeTag ───────────────────────────────────────────────────────────
|
|
78
|
+
|
|
79
|
+
const CANONICAL = ['architecture', 'typescript', 'workflow', 'testing', 'performance'];
|
|
80
|
+
|
|
81
|
+
describe('normalizeTag — mode: off', () => {
|
|
82
|
+
it('returns tag as-is regardless of canonical list', () => {
|
|
83
|
+
expect(normalizeTag('workflows', CANONICAL, 'off')).toBe('workflows');
|
|
84
|
+
});
|
|
85
|
+
|
|
86
|
+
it('passes through noise words in off mode', () => {
|
|
87
|
+
expect(normalizeTag('new', CANONICAL, 'off')).toBe('new');
|
|
88
|
+
});
|
|
89
|
+
});
|
|
90
|
+
|
|
91
|
+
describe('normalizeTag — noise stripping', () => {
|
|
92
|
+
it('drops version strings (v1.2)', () => {
|
|
93
|
+
expect(normalizeTag('v1.2', CANONICAL, 'suggest')).toBeNull();
|
|
94
|
+
});
|
|
95
|
+
|
|
96
|
+
it('drops version strings (v10)', () => {
|
|
97
|
+
expect(normalizeTag('v10', CANONICAL, 'suggest')).toBeNull();
|
|
98
|
+
});
|
|
99
|
+
|
|
100
|
+
it('drops generic noise word: new', () => {
|
|
101
|
+
expect(normalizeTag('new', CANONICAL, 'suggest')).toBeNull();
|
|
102
|
+
});
|
|
103
|
+
|
|
104
|
+
it('drops generic noise word: via', () => {
|
|
105
|
+
expect(normalizeTag('via', CANONICAL, 'suggest')).toBeNull();
|
|
106
|
+
});
|
|
107
|
+
|
|
108
|
+
it('drops generic noise word: raw', () => {
|
|
109
|
+
expect(normalizeTag('raw', CANONICAL, 'enforce')).toBeNull();
|
|
110
|
+
});
|
|
111
|
+
});
|
|
112
|
+
|
|
113
|
+
describe('normalizeTag — mode: suggest', () => {
|
|
114
|
+
it('returns canonical for exact match', () => {
|
|
115
|
+
expect(normalizeTag('typescript', CANONICAL, 'suggest')).toBe('typescript');
|
|
116
|
+
});
|
|
117
|
+
|
|
118
|
+
it('maps within edit-distance 2 to canonical', () => {
|
|
119
|
+
// 'workflows' is distance 1 from 'workflow'
|
|
120
|
+
expect(normalizeTag('workflows', CANONICAL, 'suggest')).toBe('workflow');
|
|
121
|
+
});
|
|
122
|
+
|
|
123
|
+
it('lowercases tag before matching', () => {
|
|
124
|
+
expect(normalizeTag('TypeScript', CANONICAL, 'suggest')).toBe('typescript');
|
|
125
|
+
});
|
|
126
|
+
|
|
127
|
+
it('passes through unknown tag with no close canonical (suggest passthrough)', () => {
|
|
128
|
+
const result = normalizeTag('gamification', CANONICAL, 'suggest');
|
|
129
|
+
// No match within distance 2 — passthrough
|
|
130
|
+
expect(result).toBe('gamification');
|
|
131
|
+
});
|
|
132
|
+
|
|
133
|
+
it('returns null for noise even in suggest mode', () => {
|
|
134
|
+
expect(normalizeTag('one', CANONICAL, 'suggest')).toBeNull();
|
|
135
|
+
});
|
|
136
|
+
});
|
|
137
|
+
|
|
138
|
+
describe('normalizeTag — mode: enforce', () => {
|
|
139
|
+
it('returns canonical for exact match', () => {
|
|
140
|
+
expect(normalizeTag('testing', CANONICAL, 'enforce')).toBe('testing');
|
|
141
|
+
});
|
|
142
|
+
|
|
143
|
+
it('maps within edit-distance 3 to canonical', () => {
|
|
144
|
+
// 'archtecrure' is 2 away from 'architecture'
|
|
145
|
+
expect(normalizeTag('archtecrure', CANONICAL, 'enforce')).toBe('architecture');
|
|
146
|
+
});
|
|
147
|
+
|
|
148
|
+
it('returns null for tag with no match within distance 3', () => {
|
|
149
|
+
// 'gamification' is far from all CANONICAL entries
|
|
150
|
+
expect(normalizeTag('gamification', CANONICAL, 'enforce')).toBeNull();
|
|
151
|
+
});
|
|
152
|
+
|
|
153
|
+
it('returns null for noise words', () => {
|
|
154
|
+
expect(normalizeTag('full', CANONICAL, 'enforce')).toBeNull();
|
|
155
|
+
});
|
|
156
|
+
});
|
|
157
|
+
|
|
158
|
+
describe('normalizeTag — empty canonical list', () => {
|
|
159
|
+
it('suggest mode: passes through non-noise tags', () => {
|
|
160
|
+
expect(normalizeTag('react', [], 'suggest')).toBe('react');
|
|
161
|
+
});
|
|
162
|
+
|
|
163
|
+
it('enforce mode: drops all tags (no canonical to match)', () => {
|
|
164
|
+
expect(normalizeTag('react', [], 'enforce')).toBeNull();
|
|
165
|
+
});
|
|
166
|
+
});
|
|
167
|
+
|
|
168
|
+
// ─── normalizeTags ──────────────────────────────────────────────────────────
|
|
169
|
+
|
|
170
|
+
describe('normalizeTags', () => {
|
|
171
|
+
it('deduplicates tags that map to the same canonical', () => {
|
|
172
|
+
// Both 'workflows' and 'workflow' normalize to 'workflow'
|
|
173
|
+
const result = normalizeTags(['workflows', 'workflow'], CANONICAL, 'suggest');
|
|
174
|
+
expect(result).toEqual(['workflow']);
|
|
175
|
+
});
|
|
176
|
+
|
|
177
|
+
it('preserves metadata tags unchanged', () => {
|
|
178
|
+
const result = normalizeTags(['source:article', 'typescript'], CANONICAL, 'enforce', [
|
|
179
|
+
'source:',
|
|
180
|
+
]);
|
|
181
|
+
expect(result).toContain('source:article');
|
|
182
|
+
expect(result).toContain('typescript');
|
|
183
|
+
});
|
|
184
|
+
|
|
185
|
+
it('metadata tags bypass canonical normalization in enforce mode', () => {
|
|
186
|
+
// 'source:mytype' does not match any canonical — but it should be kept
|
|
187
|
+
const result = normalizeTags(['source:mytype'], CANONICAL, 'enforce', ['source:']);
|
|
188
|
+
expect(result).toEqual(['source:mytype']);
|
|
189
|
+
});
|
|
190
|
+
|
|
191
|
+
it('returns empty array when all tags are noise', () => {
|
|
192
|
+
const result = normalizeTags(['new', 'via', 'raw', 'v1.2'], CANONICAL, 'suggest');
|
|
193
|
+
expect(result).toEqual([]);
|
|
194
|
+
});
|
|
195
|
+
|
|
196
|
+
it('in off mode, returns tags unchanged', () => {
|
|
197
|
+
const input = ['new', 'workflows', 'v1.2'];
|
|
198
|
+
const result = normalizeTags(input, CANONICAL, 'off');
|
|
199
|
+
expect(result).toEqual(input);
|
|
200
|
+
});
|
|
201
|
+
|
|
202
|
+
it('batch normalizes a mixed tag list', () => {
|
|
203
|
+
const result = normalizeTags(
|
|
204
|
+
['TypeScript', 'workflows', 'new', 'source:article'],
|
|
205
|
+
CANONICAL,
|
|
206
|
+
'suggest',
|
|
207
|
+
['source:'],
|
|
208
|
+
);
|
|
209
|
+
expect(result).toContain('typescript');
|
|
210
|
+
expect(result).toContain('workflow');
|
|
211
|
+
expect(result).toContain('source:article');
|
|
212
|
+
expect(result).not.toContain('new');
|
|
213
|
+
});
|
|
214
|
+
});
|
|
@@ -0,0 +1,188 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Tag Normalizer — canonical tag taxonomy enforcement.
|
|
3
|
+
*
|
|
4
|
+
* Maps raw tags to nearest canonical via Levenshtein edit-distance.
|
|
5
|
+
* Strips noise words (version strings, single generic words).
|
|
6
|
+
* Respects metadata tag prefixes (e.g. 'source:').
|
|
7
|
+
*
|
|
8
|
+
* Three modes:
|
|
9
|
+
* - 'enforce': must match within edit-distance 3, else drop (return null)
|
|
10
|
+
* - 'suggest': map to nearest canonical within edit-distance 2 (passthrough if no match)
|
|
11
|
+
* - 'off': no normalization — return tag as-is
|
|
12
|
+
*/
|
|
13
|
+
|
|
14
|
+
// ─── Noise filter ────────────────────────────────────────────────────────────
|
|
15
|
+
|
|
16
|
+
/**
|
|
17
|
+
* Version-string pattern: v1.2, v10, v1.2.3, etc.
|
|
18
|
+
*/
|
|
19
|
+
const VERSION_PATTERN = /^v\d+(\.\d+)*/i;
|
|
20
|
+
|
|
21
|
+
/**
|
|
22
|
+
* Generic single words that add no signal.
|
|
23
|
+
*/
|
|
24
|
+
const NOISE_WORDS = new Set([
|
|
25
|
+
'one',
|
|
26
|
+
'via',
|
|
27
|
+
'new',
|
|
28
|
+
'full',
|
|
29
|
+
'actual',
|
|
30
|
+
'raw',
|
|
31
|
+
'the',
|
|
32
|
+
'and',
|
|
33
|
+
'for',
|
|
34
|
+
'with',
|
|
35
|
+
'this',
|
|
36
|
+
'that',
|
|
37
|
+
'from',
|
|
38
|
+
'into',
|
|
39
|
+
]);
|
|
40
|
+
|
|
41
|
+
function isNoisy(tag: string): boolean {
|
|
42
|
+
if (VERSION_PATTERN.test(tag)) return true;
|
|
43
|
+
if (NOISE_WORDS.has(tag.toLowerCase())) return true;
|
|
44
|
+
return false;
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
// ─── Levenshtein edit distance ───────────────────────────────────────────────
|
|
48
|
+
|
|
49
|
+
/**
|
|
50
|
+
* Compute Levenshtein edit distance between two strings.
|
|
51
|
+
* O(m*n) time, O(n) space using two-row DP.
|
|
52
|
+
*/
|
|
53
|
+
export function computeEditDistance(a: string, b: string): number {
|
|
54
|
+
if (a === b) return 0;
|
|
55
|
+
if (a.length === 0) return b.length;
|
|
56
|
+
if (b.length === 0) return a.length;
|
|
57
|
+
|
|
58
|
+
let prev = Array.from({ length: b.length + 1 }, (_, i) => i);
|
|
59
|
+
let curr = Array.from<number>({ length: b.length + 1 });
|
|
60
|
+
|
|
61
|
+
for (let i = 1; i <= a.length; i++) {
|
|
62
|
+
curr[0] = i;
|
|
63
|
+
for (let j = 1; j <= b.length; j++) {
|
|
64
|
+
const cost = a[i - 1] === b[j - 1] ? 0 : 1;
|
|
65
|
+
curr[j] = Math.min(
|
|
66
|
+
curr[j - 1] + 1, // insertion
|
|
67
|
+
prev[j] + 1, // deletion
|
|
68
|
+
prev[j - 1] + cost, // substitution
|
|
69
|
+
);
|
|
70
|
+
}
|
|
71
|
+
[prev, curr] = [curr, prev];
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
return prev[b.length];
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
// ─── Metadata tag check ──────────────────────────────────────────────────────
|
|
78
|
+
|
|
79
|
+
/**
|
|
80
|
+
* Returns true if the tag starts with any of the given prefixes.
|
|
81
|
+
* Metadata tags (e.g. 'source:article') are exempt from canonical normalization.
|
|
82
|
+
*/
|
|
83
|
+
export function isMetadataTag(tag: string, prefixes: string[]): boolean {
|
|
84
|
+
return prefixes.some((prefix) => tag.startsWith(prefix));
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
// ─── Single tag normalization ────────────────────────────────────────────────
|
|
88
|
+
|
|
89
|
+
/**
|
|
90
|
+
* Normalize a single tag against a canonical list.
|
|
91
|
+
*
|
|
92
|
+
* @param tag - Raw tag to normalize
|
|
93
|
+
* @param canonical - Canonical tag list to map against
|
|
94
|
+
* @param mode - Constraint mode: 'enforce' | 'suggest' | 'off'
|
|
95
|
+
* @returns Normalized tag string, or null if the tag should be dropped.
|
|
96
|
+
*/
|
|
97
|
+
export function normalizeTag(
|
|
98
|
+
tag: string,
|
|
99
|
+
canonical: string[],
|
|
100
|
+
mode: 'enforce' | 'suggest' | 'off',
|
|
101
|
+
): string | null {
|
|
102
|
+
if (mode === 'off') return tag;
|
|
103
|
+
|
|
104
|
+
const lower = tag.toLowerCase().trim();
|
|
105
|
+
|
|
106
|
+
// Always drop noise words
|
|
107
|
+
if (isNoisy(lower)) return null;
|
|
108
|
+
|
|
109
|
+
// Derive lowercase canonical for matching; preserve original casing for return
|
|
110
|
+
const canonicalLower = canonical.map((x) => x.toLowerCase());
|
|
111
|
+
|
|
112
|
+
// Exact match in canonical list — return canonical form (original casing)
|
|
113
|
+
const exactIdx = canonicalLower.indexOf(lower);
|
|
114
|
+
if (exactIdx !== -1) return canonical[exactIdx];
|
|
115
|
+
|
|
116
|
+
if (canonical.length === 0) {
|
|
117
|
+
// No canonical list configured — pass through in suggest, drop in enforce
|
|
118
|
+
return mode === 'enforce' ? null : lower;
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
// Find nearest canonical by edit distance
|
|
122
|
+
let bestMatch: string | null = null;
|
|
123
|
+
let bestDist = Infinity;
|
|
124
|
+
|
|
125
|
+
for (let i = 0; i < canonicalLower.length; i++) {
|
|
126
|
+
const dist = computeEditDistance(lower, canonicalLower[i]);
|
|
127
|
+
if (dist < bestDist) {
|
|
128
|
+
bestDist = dist;
|
|
129
|
+
bestMatch = canonical[i];
|
|
130
|
+
}
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
const threshold = mode === 'enforce' ? 3 : 2;
|
|
134
|
+
|
|
135
|
+
if (bestDist <= threshold && bestMatch !== null) {
|
|
136
|
+
return bestMatch;
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
// No close match found
|
|
140
|
+
if (mode === 'enforce') {
|
|
141
|
+
return null; // drop the tag
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
// 'suggest' mode — keep original tag unchanged (passthrough)
|
|
145
|
+
return lower;
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
// ─── Batch tag normalization ─────────────────────────────────────────────────
|
|
149
|
+
|
|
150
|
+
/**
|
|
151
|
+
* Normalize a batch of tags against a canonical list.
|
|
152
|
+
* Filters out nulls (dropped tags). Deduplicates the result.
|
|
153
|
+
*
|
|
154
|
+
* @param tags - Raw tags
|
|
155
|
+
* @param canonical - Canonical tag list
|
|
156
|
+
* @param mode - Constraint mode
|
|
157
|
+
* @param metadataPrefixes - Tags with these prefixes bypass normalization
|
|
158
|
+
*/
|
|
159
|
+
export function normalizeTags(
|
|
160
|
+
tags: string[],
|
|
161
|
+
canonical: string[],
|
|
162
|
+
mode: 'enforce' | 'suggest' | 'off',
|
|
163
|
+
metadataPrefixes: string[] = ['source:'],
|
|
164
|
+
): string[] {
|
|
165
|
+
if (mode === 'off') return tags;
|
|
166
|
+
|
|
167
|
+
const seen = new Set<string>();
|
|
168
|
+
const result: string[] = [];
|
|
169
|
+
|
|
170
|
+
for (const tag of tags) {
|
|
171
|
+
// Metadata tags bypass canonical normalization but are still kept
|
|
172
|
+
if (isMetadataTag(tag, metadataPrefixes)) {
|
|
173
|
+
if (!seen.has(tag)) {
|
|
174
|
+
seen.add(tag);
|
|
175
|
+
result.push(tag);
|
|
176
|
+
}
|
|
177
|
+
continue;
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
const normalized = normalizeTag(tag, canonical, mode);
|
|
181
|
+
if (normalized !== null && !seen.has(normalized)) {
|
|
182
|
+
seen.add(normalized);
|
|
183
|
+
result.push(normalized);
|
|
184
|
+
}
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
return result;
|
|
188
|
+
}
|
|
@@ -1,5 +0,0 @@
|
|
|
1
|
-
export type { EmbeddingProvider, EmbeddingResult, EmbeddingConfig, StoredVector, EmbeddingStats, } from './types.js';
|
|
2
|
-
export { OpenAIEmbeddingProvider } from './openai-provider.js';
|
|
3
|
-
export { EmbeddingPipeline } from './pipeline.js';
|
|
4
|
-
export type { BatchEmbedOptions, BatchEmbedResult } from './pipeline.js';
|
|
5
|
-
//# sourceMappingURL=index.d.ts.map
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/embeddings/index.ts"],"names":[],"mappings":"AAAA,YAAY,EACV,iBAAiB,EACjB,eAAe,EACf,eAAe,EACf,YAAY,EACZ,cAAc,GACf,MAAM,YAAY,CAAC;AAEpB,OAAO,EAAE,uBAAuB,EAAE,MAAM,sBAAsB,CAAC;AAC/D,OAAO,EAAE,iBAAiB,EAAE,MAAM,eAAe,CAAC;AAClD,YAAY,EAAE,iBAAiB,EAAE,gBAAgB,EAAE,MAAM,eAAe,CAAC"}
|
package/dist/embeddings/index.js
DELETED
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/embeddings/index.ts"],"names":[],"mappings":"AAQA,OAAO,EAAE,uBAAuB,EAAE,MAAM,sBAAsB,CAAC;AAC/D,OAAO,EAAE,iBAAiB,EAAE,MAAM,eAAe,CAAC"}
|
|
File without changes
|
|
@@ -1,10 +0,0 @@
|
|
|
1
|
-
{
|
|
2
|
-
"id": "salvador-craft",
|
|
3
|
-
"name": "Salvador Craft — Design & Accessibility",
|
|
4
|
-
"version": "1.0.0",
|
|
5
|
-
"description": "Design system intelligence, token priority, accessibility rules, UX patterns, component patterns, and styling enforcement. Extracted from Salvador's production vault.",
|
|
6
|
-
"domains": ["design", "accessibility", "ux"],
|
|
7
|
-
"vault": {
|
|
8
|
-
"dir": "vault"
|
|
9
|
-
}
|
|
10
|
-
}
|
package/dist/knowledge-packs/knowledge-packs/salvador/salvador-craft/vault/accessibility.json
DELETED
|
@@ -1,53 +0,0 @@
|
|
|
1
|
-
{
|
|
2
|
-
"domain": "accessibility",
|
|
3
|
-
"version": "1.0.0",
|
|
4
|
-
"entries": [
|
|
5
|
-
{
|
|
6
|
-
"id": "canonical-a11y-contrast-requirements",
|
|
7
|
-
"type": "pattern",
|
|
8
|
-
"domain": "accessibility",
|
|
9
|
-
"title": "Contrast Requirements",
|
|
10
|
-
"severity": "critical",
|
|
11
|
-
"description": "## Rule\n\nAll text and meaningful UI elements MUST meet WCAG 2.1 Level AA contrast ratio requirements:\n\n| Element Type | Minimum Contrast Ratio |\n|---|---|\n| Normal text (below 18px, or below 14px bold) | **4.5:1** |\n| Large text (18px+ regular, or 14px+ bold) | **3:1** |\n| UI components and graphical objects (icons, borders, focus indicators) | **3:1** |\n\n### Specifics\n\n- **Normal text**: Any text rendered below 18px (24 CSS px at default zoom) at regular weight, or below 14px (18.66 CSS px) at bold weight, requires a **4.5:1** contrast ratio against its background.\n- **Large text**: Text at 18px+ regular weight or 14px+ bold weight requires a **3:1** contrast ratio against its background.\n- **Non-text elements**: Interactive component boundaries (input borders, button outlines), focus indicators, and informational icons require a **3:1** contrast ratio against adjacent colors.\n- **Disabled elements**: Disabled controls are exempt from contrast requirements per WCAG, but should still be perceptibly different from their enabled state.\n- **Placeholder text**: Input placeholder text must meet **4.5:1** if it conveys essential information; otherwise treat as supplementary.\n\nReference: [WCAG 2.1 Success Criterion 1.4.3 (Contrast Minimum)](https://www.w3.org/WAI/WCAG21/Understanding/contrast-minimum.html) and [SC 1.4.11 (Non-text Contrast)](https://www.w3.org/WAI/WCAG21/Understanding/non-text-contrast.html).\n\n## Why\n\n1. **Inclusivity** -- Approximately 1 in 12 men and 1 in 200 women have some form of color vision deficiency. Low contrast excludes these users.\n2. **Environmental factors** -- Users view interfaces on screens with varying brightness, in direct sunlight, and on aging displays. Adequate contrast ensures readability in all conditions.\n3. **Legal compliance** -- WCAG 2.1 AA is the accepted legal standard in many jurisdictions (ADA, EN 301 549, Section 508).\n4. **Cognitive load** -- High-contrast text is faster to read for all users, reducing fatigue and errors.\n\n## Assertions\n\n- Every foreground/background color pair used for text must produce a contrast ratio >= 4.5:1 (normal text) or >= 3.0:1 (large text).\n- Every interactive component border color must produce a contrast ratio >= 3.0:1 against adjacent non-interactive surfaces.\n- Focus indicator colors must produce a contrast ratio >= 3.0:1 against the element's unfocused state.\n- No color pair used for informational icons may have a contrast ratio below 3.0:1 against its background.\n- Automated checks must flag any computed contrast ratio below the applicable threshold.",
|
|
12
|
-
"tags": ["accessibility", "contrast", "wcag", "a11y", "color"],
|
|
13
|
-
"tier": "agent"
|
|
14
|
-
},
|
|
15
|
-
{
|
|
16
|
-
"id": "canonical-a11y-touch-targets",
|
|
17
|
-
"type": "pattern",
|
|
18
|
-
"domain": "accessibility",
|
|
19
|
-
"title": "Touch Target Sizes",
|
|
20
|
-
"severity": "critical",
|
|
21
|
-
"description": "## Rule\n\nAll interactive elements MUST meet minimum touch target sizes:\n\n| Platform | Minimum Target Size | Reference |\n|---|---|---|\n| iOS (Apple HIG) | **44 x 44 pt** | Apple Human Interface Guidelines |\n| Android / Material Design | **48 x 48 dp** | Material Design Guidelines |\n| Web (WCAG 2.5.8, AAA) | **44 x 44 CSS px** | WCAG 2.2 SC 2.5.8 Target Size (Minimum) |\n| Web (WCAG 2.5.5, AA) | **24 x 24 CSS px** minimum, 44 x 44 CSS px recommended | WCAG 2.2 SC 2.5.5 |\n\n### Spacing Between Targets\n\n- Adjacent interactive targets MUST have at least **8px** of non-interactive space between their hit areas.\n- If two targets are closer than 8px, their combined hit areas must not overlap.\n- Inline text links in body copy are exempt from size requirements but should have sufficient line height (>= 1.5) to allow accurate tapping.\n\n### Implementation Details\n\n- The touch target includes padding and any transparent hit-area extensions, not just the visible element.\n- Use CSS `min-height` and `min-width` (not just `padding`) to guarantee the minimum dimensions.\n- Icon-only buttons that render a 16px or 24px icon MUST extend their tappable area to the full minimum size via padding or an invisible hit region.\n- Dropdown menu items, list rows, and navigation links must each meet the minimum height independently.\n\n## Why\n\n1. **Motor accessibility** -- Users with motor impairments, tremors, or limited dexterity need adequately sized targets to interact reliably.\n2. **Mobile usability** -- The average adult fingertip covers roughly 10mm (~40px at standard density). Targets smaller than this cause frequent mistaps.\n3. **Speed and confidence** -- Fitts's Law shows that larger targets reduce the time and error rate for pointing tasks. Meeting minimums improves task completion speed for all users.\n4. **Platform compliance** -- Both Apple and Google reject apps from their stores that fail basic usability checks, including undersized touch targets.\n\n## Assertions\n\n- No interactive element (button, link, checkbox, radio, toggle, select, slider thumb) may have a computed tappable area smaller than 44x44 CSS px on web or 44x44 pt on iOS or 48x48 dp on Android.\n- Adjacent interactive elements must have >= 8px non-interactive separation.\n- Icon-only interactive elements with a visible icon smaller than the minimum target size must have padding or hit-area expansion to meet the minimum.\n- Elements using `onClick` or equivalent touch/click handlers must meet target size requirements.",
|
|
22
|
-
"tags": ["accessibility", "touch", "mobile", "a11y", "interaction"],
|
|
23
|
-
"tier": "agent"
|
|
24
|
-
},
|
|
25
|
-
{
|
|
26
|
-
"id": "pattern-accessibility-quick-minimum-44x44px-touch-targets-enforced-via-quality",
|
|
27
|
-
"type": "pattern",
|
|
28
|
-
"domain": "accessibility",
|
|
29
|
-
"title": "Minimum 44x44px touch targets enforced via quality rules",
|
|
30
|
-
"severity": "warning",
|
|
31
|
-
"description": "# Minimum 44x44px touch targets enforced via quality rules\n\n## Context\n\nCaptured during development session on 2026-02-21\n\n## Pattern\n\nThe ux-touch-targets rule in chains/rules/quality.yaml enforces Fitts's Law: all interactive elements must have a minimum 44x44px touch target. This applies to buttons, links, checkboxes, and any clickable area. Smaller targets cause frustration on mobile and accessibility issues for motor-impaired users. The rule fires as a warning during component validation.\n\n## Example\n\n```typescript\nWarning: Button touch target is 32x32px, minimum required is 44x44px. Add p-2 or min-h-[44px] min-w-[44px].\n```\n\n## Why\n\nWCAG 2.5.8 (Target Size) requires 44x44px minimum. This is also a core UX law (Fitts's Law) that Salvador's UX-AUDITOR-flow evaluates.",
|
|
32
|
-
"tags": ["touch-targets", "fitts-law", "mobile", "motor-accessibility"]
|
|
33
|
-
},
|
|
34
|
-
{
|
|
35
|
-
"id": "pattern-accessibility-quick-tooltip-component-required-instead-of-native-title",
|
|
36
|
-
"type": "pattern",
|
|
37
|
-
"domain": "accessibility",
|
|
38
|
-
"title": "Tooltip component required instead of native title attribute",
|
|
39
|
-
"severity": "critical",
|
|
40
|
-
"description": "# Tooltip component required instead of native title attribute\n\n## Context\n\nCaptured during development session on 2026-02-21\n\n## Pattern\n\nThe no-native-title hookify rule blocks any use of title=\"...\" on HTML elements. Native title attributes have poor accessibility: not announced by all screen readers, invisible on touch devices, no styling control. Instead, use the Tooltip/TooltipTrigger/TooltipContent component pattern which provides keyboard support, screen reader announcements, and consistent styling.\n\n## Example\n\n```typescript\nBad: <button title=\"Delete item\">X</button>. Good: <Tooltip><TooltipTrigger><button>X</button></TooltipTrigger><TooltipContent>Delete item</TooltipContent></Tooltip>\n```\n\n## Why\n\nNative title is the most commonly misused accessibility pattern. Blocking it forces the accessible alternative.",
|
|
41
|
-
"tags": ["tooltip", "title-attribute", "a11y", "screen-reader", "touch"]
|
|
42
|
-
},
|
|
43
|
-
{
|
|
44
|
-
"id": "pattern-accessibility-quick-wcag-contrast-validation-as-a-mandatory-gate-in-de",
|
|
45
|
-
"type": "pattern",
|
|
46
|
-
"domain": "accessibility",
|
|
47
|
-
"title": "WCAG contrast validation as a mandatory gate in design flows",
|
|
48
|
-
"severity": "critical",
|
|
49
|
-
"description": "# WCAG contrast validation as a mandatory gate in design flows\n\n## Context\n\nCaptured during development session on 2026-02-21\n\n## Pattern\n\nEvery color pairing must pass WCAG contrast validation before approval. Salvador enforces 3 levels: AA normal text (4.5:1), AA large text (3.0:1), AAA (7.0:1). The contrast chain is included in validate-colors composite and runs in design, review, and UX-audit flows. Known safe combos: bg-dark+text-inverse (19.41:1), bg-surface+text-primary (17.85:1). Known failures: bg-accent+text-inverse (2.98:1).\n\n## Example\n\n```typescript\nsalvador_check_contrast({ foreground: 'text-primary', background: 'bg-surface' }) → { pass: true, ratio: 17.85, level: 'AAA' }\n```\n\n## Why\n\nContrast failures are the most common accessibility violation. Automated gating catches them before they reach code review.",
|
|
50
|
-
"tags": ["wcag", "contrast", "color", "validation", "a11y"]
|
|
51
|
-
}
|
|
52
|
-
]
|
|
53
|
-
}
|
package/dist/knowledge-packs/knowledge-packs/salvador/salvador-craft/vault/design-tokens.json
DELETED
|
@@ -1,26 +0,0 @@
|
|
|
1
|
-
{
|
|
2
|
-
"domain": "design-tokens",
|
|
3
|
-
"version": "1.0.0",
|
|
4
|
-
"entries": [
|
|
5
|
-
{
|
|
6
|
-
"id": "canonical-design-color-token-priority",
|
|
7
|
-
"type": "pattern",
|
|
8
|
-
"domain": "design-tokens",
|
|
9
|
-
"title": "Color Token Priority",
|
|
10
|
-
"severity": "critical",
|
|
11
|
-
"description": "## Rule\n\nAll color usage MUST reference design tokens, never raw color values. The priority order is:\n\n1. **Semantic tokens (highest priority)** -- Tokens that convey meaning and intent.\n - Examples: `text-primary`, `text-secondary`, `text-warning`, `text-error`, `bg-surface`, `bg-error`, `border-focus`, `icon-success`.\n - Use these whenever the color communicates a specific purpose.\n\n2. **Contextual tokens** -- Tokens scoped to a component context.\n - Examples: `button-bg-primary`, `input-border-default`, `card-bg`.\n - Use when semantic tokens are too generic for a component's specific need.\n\n3. **Primitive tokens (lowest priority)** -- Named palette references.\n - Examples: `blue-600`, `gray-100`.\n - Use only when no semantic or contextual token fits, and document the reason.\n\n### Forbidden\n\nThe following are **never** permitted in source code:\n\n- Raw hex values: `#3B82F6`, `#fff`, `#000`\n- RGB/RGBA functions: `rgb(59, 130, 246)`, `rgba(0,0,0,0.5)`\n- HSL/HSLA functions: `hsl(217, 91%, 60%)`\n- Tailwind palette classes used as color decisions: `bg-blue-500`, `text-gray-700`\n- CSS named colors: `red`, `white`, `black`\n\n## Why\n\n1. **Theming** -- Semantic tokens decouple color intent from color value, enabling dark mode, high-contrast mode, and brand theming with zero code changes.\n2. **Consistency** -- When \"error\" always maps to the same token, the error color is guaranteed to be uniform across every component.\n3. **Accessibility** -- Token-level contrast validation ensures all usages meet WCAG requirements when the palette changes.\n4. **Maintainability** -- Changing a brand color updates one token definition rather than hundreds of scattered hex values.\n\n## Assertions\n\n- No source file may contain a literal hex color (`/#[0-9a-fA-F]{3,8}/`).\n- No source file may contain `rgb(`, `rgba(`, `hsl(`, or `hsla(` as a color value.\n- No Tailwind class matching `/bg-(red|blue|green|yellow|purple|pink|indigo|gray|slate|zinc|neutral|stone|orange|amber|emerald|teal|cyan|sky|violet|fuchsia|rose|lime)-\\d+/` may appear in component code.\n- Every color property in CSS/JSX must resolve to a design token reference.\n- Primitive token usage must include a code comment explaining why no semantic token fits.",
|
|
12
|
-
"tags": ["color", "tokens", "semantic", "design-tokens", "theming"],
|
|
13
|
-
"tier": "agent"
|
|
14
|
-
},
|
|
15
|
-
{
|
|
16
|
-
"id": "canonical-design-spacing-scale",
|
|
17
|
-
"type": "pattern",
|
|
18
|
-
"domain": "design-tokens",
|
|
19
|
-
"title": "Spacing Scale",
|
|
20
|
-
"severity": "critical",
|
|
21
|
-
"description": "## Rule\n\nAll spacing values (margin, padding, gap) MUST use only the following scale:\n\n| Token | Value |\n|---------|-------|\n| space-1 | 4px |\n| space-2 | 8px |\n| space-3 | 12px |\n| space-4 | 16px |\n| space-6 | 24px |\n| space-8 | 32px |\n\nNo other spacing values are permitted. Allowed values: **4, 8, 12, 16, 24, 32** (in pixels).\n\n- Use `space-1` (4px) for tight inline spacing (icon-to-label gaps, error message offset).\n- Use `space-2` (8px) for related element grouping (form fields within a group).\n- Use `space-3` (12px) for medium internal padding (card content insets).\n- Use `space-4` (16px) as the base unit for standard padding and gaps.\n- Use `space-6` (24px) for section separation within a view.\n- Use `space-8` (32px) for major section breaks and page-level margins.\n\n## Why\n\nA constrained spacing scale based on a 4px grid creates consistent visual rhythm across all screens and components. When every element snaps to the same spatial grid:\n\n1. **Visual consistency** -- Users perceive the interface as polished and intentional. Arbitrary values (e.g., 5px, 13px, 22px) create subtle misalignments that accumulate into visual noise.\n2. **Faster design decisions** -- Designers and developers choose from 6 values instead of infinite possibilities, reducing bikeshedding.\n3. **Predictable responsiveness** -- A base-4 scale divides evenly across common breakpoints and container sizes.\n4. **Maintainability** -- Changing the scale in one place propagates cleanly; random values require auditing every usage.\n\n## Assertions\n\n- No CSS property `margin`, `padding`, `gap`, `top`, `right`, `bottom`, `left`, `inset`, or `row-gap`/`column-gap` may contain a value outside the set `[4, 8, 12, 16, 24, 32]` (px) or their rem equivalents `[0.25, 0.5, 0.75, 1, 1.5, 2]`.\n- Zero (`0`) is always permitted (no spacing).\n- Negative margins are allowed only if the absolute value is in the allowed set.\n- Token names matching `/space-/` must resolve to one of the allowed values.",
|
|
22
|
-
"tags": ["spacing", "layout", "rhythm", "design-tokens"],
|
|
23
|
-
"tier": "agent"
|
|
24
|
-
}
|
|
25
|
-
]
|
|
26
|
-
}
|
|
@@ -1,33 +0,0 @@
|
|
|
1
|
-
{
|
|
2
|
-
"domain": "design",
|
|
3
|
-
"version": "1.0.0",
|
|
4
|
-
"entries": [
|
|
5
|
-
{
|
|
6
|
-
"id": "pattern-design-quick-component-variant-enum-over-boolean-prop-explosion",
|
|
7
|
-
"type": "pattern",
|
|
8
|
-
"domain": "design",
|
|
9
|
-
"title": "Component variant enum over boolean prop explosion",
|
|
10
|
-
"severity": "warning",
|
|
11
|
-
"description": "# Component variant enum over boolean prop explosion\n\n## Context\n\nCaptured during development session on 2026-02-21\n\n## Pattern\n\nDesign system components must use a single variant enum prop instead of multiple boolean props. Boolean explosion: isLarge, isBold, isPrimary creates 2^3=8 combinations, most invalid. Variant enum: variant='primary'|'secondary'|'destructive'|'outline'|'ghost' makes valid states explicit and invalid states unrepresentable. This pattern is enforced via api-constraints intelligence data and component dev rules.\n\n## Example\n\n```typescript\nBad: <Button isLarge isPrimary isBold>. Good: <Button variant='primary' size='lg'>\n```\n\n## Why\n\nBoolean props create combinatorial explosion of untested states. Variant enums make the valid state space explicit and testable.",
|
|
12
|
-
"tags": ["variants", "api-design", "boolean-explosion", "type-safety", "components"]
|
|
13
|
-
},
|
|
14
|
-
{
|
|
15
|
-
"id": "pattern-design-quick-atomic-design-classification-5-levels-from-atom-to",
|
|
16
|
-
"type": "rule",
|
|
17
|
-
"domain": "design",
|
|
18
|
-
"title": "Atomic design classification: 5 levels from atom to page",
|
|
19
|
-
"severity": "critical",
|
|
20
|
-
"description": "# Atomic design classification: 5 levels from atom to page\n\n## Context\n\nCaptured during development session on 2026-02-21\n\n## Pattern\n\nSalvador classifies all UI elements into 5 atomic design levels defined in chains/domains/ui-components.yaml. Atom: single-responsibility elements (Button, Input, Icon). Molecule: small groups (FormField = label+input+error). Organism: complex sections (Form, Modal, DataTable). Template: page layouts (DashboardLayout). Page: full screens (LoginPage). Classification drives UX law selection in UX-AUDITOR-flow — atoms get Fitts's Law, organisms get Hick's Law.\n\n## Example\n\n```typescript\nButton (atom) → FormField (molecule) → LoginForm (organism) → AuthLayout (template) → LoginPage (page)\n```\n\n## Why\n\nAtomic level determines which UX laws apply, which validation chains run, and how components compose. Skipping levels creates maintenance debt.",
|
|
21
|
-
"tags": ["atomic-design", "classification", "component-hierarchy", "ux-laws"]
|
|
22
|
-
},
|
|
23
|
-
{
|
|
24
|
-
"id": "pattern-design-quick-semantic-color-context-mapping-with-depth-levels",
|
|
25
|
-
"type": "rule",
|
|
26
|
-
"domain": "design",
|
|
27
|
-
"title": "Semantic color context mapping with depth levels",
|
|
28
|
-
"severity": "critical",
|
|
29
|
-
"description": "# Semantic color context mapping with depth levels\n\n## Context\n\nCaptured during development session on 2026-02-21\n\n## Pattern\n\nColor intelligence uses context-aware depth levels for semantic color assignment. Page (depth 0): broadest context, lightest backgrounds. Card (depth 2): elevated surface, subtle distinction. Surface (depth 3): interactive or nested areas. Button (depth 4): highest contrast. Each depth defines text color hierarchy (primary > secondary > muted) and valid background/foreground pairs. The validate-colors composite checks both light and dark mode variants at each depth.\n\n## Example\n\n```typescript\nPage depth-0: bg-page + text-primary. Card depth-2: bg-card + text-primary. Button depth-4: bg-primary-500 + text-inverse.\n```\n\n## Why\n\nContext-aware colors prevent accessibility failures when nesting components. A text color valid on a page background may fail on a card background.",
|
|
30
|
-
"tags": ["color", "context", "depth-levels", "semantic", "dark-mode"]
|
|
31
|
-
}
|
|
32
|
-
]
|
|
33
|
-
}
|