@lifeonlars/prime-yggdrasil 0.3.0 → 0.4.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.
@@ -0,0 +1,187 @@
1
+ /**
2
+ * Interaction Patterns Rule: Focus Management
3
+ *
4
+ * Validates proper focus management in Dialogs, Modals, and form flows.
5
+ * Ensures keyboard navigation works correctly and focus is trapped/returned appropriately.
6
+ *
7
+ * Phase 6 - Interaction Patterns Agent
8
+ */
9
+
10
+ export default {
11
+ name: 'interaction-patterns/focus-management',
12
+ description: 'Validate Dialog/Modal focus patterns and keyboard navigation',
13
+ category: 'interaction-patterns',
14
+ severity: 'warning',
15
+ documentation: 'https://github.com/lifeonlars/prime-yggdrasil/blob/master/.ai/agents/interaction-patterns.md#5-focus-management',
16
+
17
+ validate(fileContent, filePath) {
18
+ const violations = [];
19
+
20
+ // Skip non-React/TSX files
21
+ if (!filePath.match(/\.(jsx|tsx|js|ts)$/)) {
22
+ return violations;
23
+ }
24
+
25
+ // Pattern 1: Check Dialog components for focus management
26
+ const dialogRegex = /<Dialog[^>]*>/g;
27
+ let match;
28
+
29
+ while ((match = dialogRegex.exec(fileContent)) !== null) {
30
+ const dialogStart = match.index;
31
+ const dialogEnd = fileContent.indexOf('</Dialog>', dialogStart);
32
+
33
+ if (dialogEnd === -1) continue;
34
+
35
+ const dialogContent = fileContent.substring(dialogStart, dialogEnd);
36
+ const line = this.getLineNumber(fileContent, dialogStart);
37
+
38
+ // Check 1: Dialog should have modal prop (focus trap)
39
+ if (!dialogContent.includes('modal')) {
40
+ violations.push({
41
+ line,
42
+ column: 1,
43
+ message: 'Dialog missing modal prop. Add modal={true} to trap focus within dialog.',
44
+ severity: 'warning',
45
+ rule: this.name,
46
+ suggestion: '<Dialog visible={visible} onHide={onHide} modal>',
47
+ });
48
+ }
49
+
50
+ // Check 2: First interactive element should have autoFocus
51
+ const hasAutoFocus = /autoFocus/.test(dialogContent);
52
+ const hasInteractiveElement = /(<InputText|<Button|<Dropdown|<Calendar|<Checkbox)/.test(dialogContent);
53
+
54
+ if (hasInteractiveElement && !hasAutoFocus) {
55
+ violations.push({
56
+ line,
57
+ column: 1,
58
+ message: 'Dialog first interactive element should have autoFocus for keyboard navigation.',
59
+ severity: 'info',
60
+ rule: this.name,
61
+ suggestion: '<InputText autoFocus /> // First field in dialog',
62
+ });
63
+ }
64
+
65
+ // Check 3: Dialog should have onHide handler (ESC key support)
66
+ if (!dialogContent.includes('onHide')) {
67
+ violations.push({
68
+ line,
69
+ column: 1,
70
+ message: 'Dialog missing onHide handler. Required for ESC key support.',
71
+ severity: 'error',
72
+ rule: this.name,
73
+ suggestion: '<Dialog visible={visible} onHide={() => setVisible(false)}>',
74
+ });
75
+ }
76
+ }
77
+
78
+ // Pattern 2: Check for interactive divs without keyboard handlers
79
+ const interactiveDivRegex = /<div[^>]*onClick[^>]*>/g;
80
+
81
+ while ((match = interactiveDivRegex.exec(fileContent)) !== null) {
82
+ const divTag = match[0];
83
+ const line = this.getLineNumber(fileContent, match.index);
84
+
85
+ // Check if div has keyboard handlers
86
+ const hasKeyboard = divTag.includes('onKeyDown') || divTag.includes('onKeyPress');
87
+ const hasRole = divTag.includes('role="button"') || divTag.includes('role=\\"button\\"');
88
+ const hasTabIndex = divTag.includes('tabIndex');
89
+
90
+ if (!hasKeyboard || !hasRole || !hasTabIndex) {
91
+ violations.push({
92
+ line,
93
+ column: 1,
94
+ message: 'Interactive div must have role="button", tabIndex, and keyboard handlers (onKeyDown).',
95
+ severity: 'error',
96
+ rule: this.name,
97
+ suggestion: this.getKeyboardHandlerSuggestion(),
98
+ });
99
+ }
100
+ }
101
+
102
+ // Pattern 3: Check form fields for logical tab order
103
+ const formRegex = /<form[^>]*>/g;
104
+
105
+ while ((match = formRegex.exec(fileContent)) !== null) {
106
+ const formStart = match.index;
107
+ const formEnd = fileContent.indexOf('</form>', formStart);
108
+
109
+ if (formEnd === -1) continue;
110
+
111
+ const formContent = fileContent.substring(formStart, formEnd);
112
+ const line = this.getLineNumber(fileContent, formStart);
113
+
114
+ // Check for explicit tabIndex that might break natural order
115
+ const explicitTabIndex = /tabIndex=\{?(\d+)\}?/.exec(formContent);
116
+
117
+ if (explicitTabIndex && parseInt(explicitTabIndex[1]) > 0) {
118
+ violations.push({
119
+ line: line + this.getLineNumber(formContent, explicitTabIndex.index),
120
+ column: 1,
121
+ message: 'Avoid positive tabIndex values. Use natural DOM order or tabIndex={0} for custom elements.',
122
+ severity: 'warning',
123
+ rule: this.name,
124
+ suggestion: 'Remove tabIndex or use tabIndex={0} for same-level focus order.',
125
+ });
126
+ }
127
+ }
128
+
129
+ // Pattern 4: Check for custom focus styles
130
+ const hasFocusStyles = /:focus/.test(fileContent) || /focus-visible/.test(fileContent);
131
+ const hasInteractiveComponents = /<(Button|InputText|Dropdown|Calendar)/.test(fileContent);
132
+
133
+ if (hasInteractiveComponents && fileContent.includes('outline: none') && !hasFocusStyles) {
134
+ const line = this.getLineNumber(fileContent, fileContent.indexOf('outline: none'));
135
+
136
+ violations.push({
137
+ line,
138
+ column: 1,
139
+ message: 'Removing outline without custom focus styles breaks keyboard navigation visibility.',
140
+ severity: 'error',
141
+ rule: this.name,
142
+ suggestion: 'Replace with: boxShadow: "0 0 0 2px var(--border-state-focus)"',
143
+ });
144
+ }
145
+
146
+ return violations;
147
+ },
148
+
149
+ getKeyboardHandlerSuggestion() {
150
+ return `
151
+ <div
152
+ role="button"
153
+ tabIndex={0}
154
+ onClick={handleClick}
155
+ onKeyDown={e => (e.key === 'Enter' || e.key === ' ') && handleClick()}
156
+ >
157
+ Click me
158
+ </div>`;
159
+ },
160
+
161
+ getLineNumber(content, index) {
162
+ return content.substring(0, index).split('\n').length;
163
+ },
164
+
165
+ autofix(fileContent, violation) {
166
+ if (violation.message.includes('Dialog missing modal prop')) {
167
+ const dialogRegex = /<Dialog([^>]*)>/;
168
+ const match = dialogRegex.exec(fileContent);
169
+
170
+ if (match) {
171
+ const dialogTag = match[0];
172
+ const fixedTag = dialogTag.replace('<Dialog', '<Dialog modal');
173
+
174
+ return {
175
+ fixed: true,
176
+ content: fileContent.replace(dialogTag, fixedTag),
177
+ message: 'Added modal prop to Dialog',
178
+ };
179
+ }
180
+ }
181
+
182
+ return {
183
+ fixed: false,
184
+ message: 'Manual fix required. See suggestion in violation message.',
185
+ };
186
+ },
187
+ };
@@ -0,0 +1,190 @@
1
+ /**
2
+ * Interaction Patterns Rule: Generic Copy
3
+ *
4
+ * Enforces specific, action-oriented button labels and copy instead of generic terms.
5
+ * Aligns with AESTHETICS.md: "Be specific, be concise, be functional"
6
+ *
7
+ * Phase 6 - Interaction Patterns Agent
8
+ */
9
+
10
+ export default {
11
+ name: 'interaction-patterns/generic-copy',
12
+ description: 'Enforce specific button labels over generic ones ("Save Changes" not "Submit")',
13
+ category: 'interaction-patterns',
14
+ severity: 'warning',
15
+ documentation: 'https://github.com/lifeonlars/prime-yggdrasil/blob/master/.ai/agents/interaction-patterns.md#7-copy-tone--content',
16
+
17
+ // Generic terms to avoid
18
+ genericTerms: {
19
+ // Button labels
20
+ 'Submit': 'Use specific action like "Save Changes", "Create Account", "Send Message"',
21
+ 'OK': 'Use specific action like "Confirm Delete", "Apply Settings", "Close"',
22
+ 'Cancel': 'OK in most cases, but consider "Keep Editing", "Go Back" for clarity',
23
+ 'Continue': 'Use specific action like "Next: Payment", "Proceed to Checkout"',
24
+ 'Done': 'Use specific action like "Finish Setup", "Complete"',
25
+ 'Click here': 'Use descriptive link text like "View documentation", "Learn more about tokens"',
26
+ 'Click': 'Avoid instructional copy - buttons are self-evident',
27
+
28
+ // Messages
29
+ 'Success!': 'Be specific: "Settings saved", "User created", "Email sent"',
30
+ 'Error': 'Explain what failed: "Unable to save. Try again.", "Connection failed."',
31
+ 'Warning': 'Be specific about the warning: "Unsaved changes will be lost"',
32
+ 'Oops!': 'Avoid casual tone - be clear and pragmatic',
33
+ 'Something went wrong': 'Explain the issue: "Unable to connect to server", "Invalid format"',
34
+ 'Loading...': 'Be specific: "Loading users...", "Saving changes...", "Processing..."',
35
+ },
36
+
37
+ validate(fileContent, filePath) {
38
+ const violations = [];
39
+
40
+ // Skip non-React/TSX files
41
+ if (!filePath.match(/\.(jsx|tsx|js|ts)$/)) {
42
+ return violations;
43
+ }
44
+
45
+ // Pattern 1: Check Button component labels
46
+ const buttonLabelRegex = /<Button[^>]*label=["']([^"']+)["']/g;
47
+ let match;
48
+
49
+ while ((match = buttonLabelRegex.exec(fileContent)) !== null) {
50
+ const label = match[1];
51
+ const line = this.getLineNumber(fileContent, match.index);
52
+
53
+ if (this.genericTerms[label]) {
54
+ violations.push({
55
+ line,
56
+ column: 1,
57
+ message: `Generic button label "${label}". ${this.genericTerms[label]}`,
58
+ severity: 'warning',
59
+ rule: this.name,
60
+ suggestion: this.getSuggestion(label),
61
+ });
62
+ }
63
+ }
64
+
65
+ // Pattern 2: Check button element text content
66
+ const buttonTextRegex = /<button[^>]*>([^<]+)<\/button>/gi;
67
+
68
+ while ((match = buttonTextRegex.exec(fileContent)) !== null) {
69
+ const text = match[1].trim();
70
+ const line = this.getLineNumber(fileContent, match.index);
71
+
72
+ if (this.genericTerms[text]) {
73
+ violations.push({
74
+ line,
75
+ column: 1,
76
+ message: `Generic button text "${text}". ${this.genericTerms[text]}`,
77
+ severity: 'warning',
78
+ rule: this.name,
79
+ suggestion: this.getSuggestion(text),
80
+ });
81
+ }
82
+ }
83
+
84
+ // Pattern 3: Check Message/Toast content
85
+ const messagePatterns = [
86
+ /severity=["']success["'][^>]*text=["']([^"']+)["']/g,
87
+ /severity=["']error["'][^>]*text=["']([^"']+)["']/g,
88
+ /severity=["']warn["'][^>]*text=["']([^"']+)["']/g,
89
+ /summary=["']([^"']+)["']/g,
90
+ ];
91
+
92
+ messagePatterns.forEach(pattern => {
93
+ while ((match = pattern.exec(fileContent)) !== null) {
94
+ const text = match[1];
95
+ const line = this.getLineNumber(fileContent, match.index);
96
+
97
+ if (this.genericTerms[text]) {
98
+ violations.push({
99
+ line,
100
+ column: 1,
101
+ message: `Generic message "${text}". ${this.genericTerms[text]}`,
102
+ severity: 'warning',
103
+ rule: this.name,
104
+ suggestion: this.getSuggestion(text),
105
+ });
106
+ }
107
+
108
+ // Check for emoji/fluffy language
109
+ if (/[🎉😅❤️👍✨]/.test(text)) {
110
+ violations.push({
111
+ line,
112
+ column: 1,
113
+ message: `Avoid emoji in UI copy. Keep tone clear and pragmatic.`,
114
+ severity: 'warning',
115
+ rule: this.name,
116
+ suggestion: `Remove emoji from: "${text}"`,
117
+ });
118
+ }
119
+
120
+ // Check for casual/marketing language
121
+ const casualPhrases = [
122
+ /let's/i,
123
+ /awesome/i,
124
+ /amazing/i,
125
+ /yay/i,
126
+ /congrats/i,
127
+ /you're all set/i,
128
+ ];
129
+
130
+ casualPhrases.forEach(phrase => {
131
+ if (phrase.test(text)) {
132
+ violations.push({
133
+ line,
134
+ column: 1,
135
+ message: `Avoid casual/marketing language in UI copy: "${text}"`,
136
+ severity: 'warning',
137
+ rule: this.name,
138
+ suggestion: 'Use clear, functional copy. See docs/AESTHETICS.md for examples.',
139
+ });
140
+ }
141
+ });
142
+ }
143
+ });
144
+
145
+ // Pattern 4: Check link text
146
+ const linkTextRegex = /<a[^>]*>([^<]+)<\/a>/gi;
147
+
148
+ while ((match = linkTextRegex.exec(fileContent)) !== null) {
149
+ const text = match[1].trim();
150
+ const line = this.getLineNumber(fileContent, match.index);
151
+
152
+ if (this.genericTerms[text]) {
153
+ violations.push({
154
+ line,
155
+ column: 1,
156
+ message: `Generic link text "${text}". ${this.genericTerms[text]}`,
157
+ severity: 'warning',
158
+ rule: this.name,
159
+ suggestion: 'Use descriptive link text that explains the destination.',
160
+ });
161
+ }
162
+ }
163
+
164
+ return violations;
165
+ },
166
+
167
+ getSuggestion(genericTerm) {
168
+ const examples = {
169
+ 'Submit': 'Examples: "Save Changes", "Create Project", "Send Email"',
170
+ 'OK': 'Examples: "Confirm", "Apply", "Close Dialog"',
171
+ 'Continue': 'Examples: "Next: Payment", "Proceed to Review"',
172
+ 'Success!': 'Examples: "Settings saved", "User created successfully"',
173
+ 'Error': 'Examples: "Unable to save. Try again.", "Invalid email format."',
174
+ };
175
+
176
+ return examples[genericTerm] || 'Use specific, action-oriented copy.';
177
+ },
178
+
179
+ getLineNumber(content, index) {
180
+ return content.substring(0, index).split('\n').length;
181
+ },
182
+
183
+ autofix(fileContent, violation) {
184
+ // No automatic fix for copy - requires human judgment
185
+ return {
186
+ fixed: false,
187
+ message: 'Manual fix required. Replace generic copy with specific, action-oriented text.',
188
+ };
189
+ },
190
+ };
@@ -0,0 +1,17 @@
1
+ /**
2
+ * Interaction Patterns Rules
3
+ *
4
+ * Phase 6 - Behavioral consistency enforcement
5
+ *
6
+ * All rules enforce patterns from .ai/agents/interaction-patterns.md
7
+ */
8
+
9
+ import stateCompleteness from './state-completeness.js';
10
+ import genericCopy from './generic-copy.js';
11
+ import focusManagement from './focus-management.js';
12
+
13
+ export default {
14
+ 'interaction-patterns/state-completeness': stateCompleteness,
15
+ 'interaction-patterns/generic-copy': genericCopy,
16
+ 'interaction-patterns/focus-management': focusManagement,
17
+ };
@@ -0,0 +1,194 @@
1
+ /**
2
+ * Interaction Patterns Rule: State Completeness
3
+ *
4
+ * Enforces that async operations and data-driven components handle all required states:
5
+ * - Loading state
6
+ * - Error state
7
+ * - Empty state
8
+ * - Disabled state (when applicable)
9
+ *
10
+ * Phase 6 - Interaction Patterns Agent
11
+ */
12
+
13
+ export default {
14
+ name: 'interaction-patterns/state-completeness',
15
+ description: 'Enforce state completeness (loading/error/empty/disabled) for async operations',
16
+ category: 'interaction-patterns',
17
+ severity: 'warning',
18
+ documentation: 'https://github.com/lifeonlars/prime-yggdrasil/blob/master/.ai/agents/interaction-patterns.md#1-state-management-patterns',
19
+
20
+ validate(fileContent, filePath) {
21
+ const violations = [];
22
+
23
+ // Skip non-React/TSX files
24
+ if (!filePath.match(/\.(jsx|tsx|js|ts)$/)) {
25
+ return violations;
26
+ }
27
+
28
+ // Pattern 1: Detect async operations (useEffect, fetch, axios, async functions)
29
+ const asyncPatterns = [
30
+ /useEffect\s*\(/g,
31
+ /async\s+function/g,
32
+ /async\s+\(/g,
33
+ /\.then\s*\(/g,
34
+ /await\s+fetch/g,
35
+ /await\s+axios/g,
36
+ ];
37
+
38
+ const hasAsyncOperation = asyncPatterns.some(pattern => pattern.test(fileContent));
39
+
40
+ if (!hasAsyncOperation) {
41
+ // No async operations, skip validation
42
+ return violations;
43
+ }
44
+
45
+ // Pattern 2: Check for state variables that suggest data loading
46
+ const statePatterns = {
47
+ loading: /const\s+\[\s*\w*loading\w*\s*,/gi,
48
+ error: /const\s+\[\s*\w*error\w*\s*,/gi,
49
+ data: /const\s+\[\s*\w*(data|items|users|records)\w*\s*,/gi,
50
+ };
51
+
52
+ const hasLoadingState = statePatterns.loading.test(fileContent);
53
+ const hasErrorState = statePatterns.error.test(fileContent);
54
+ const hasDataState = statePatterns.data.test(fileContent);
55
+
56
+ // Pattern 3: Detect DataTable, List, or other data-driven components
57
+ const dataComponents = [
58
+ 'DataTable',
59
+ 'DataView',
60
+ 'Tree',
61
+ 'TreeTable',
62
+ 'OrderList',
63
+ 'PickList',
64
+ ];
65
+
66
+ const hasDataComponent = dataComponents.some(component =>
67
+ fileContent.includes(`<${component}`)
68
+ );
69
+
70
+ // Violation detection
71
+ if (hasAsyncOperation || hasDataComponent) {
72
+ const missingStates = [];
73
+
74
+ if (!hasLoadingState) {
75
+ missingStates.push('loading');
76
+ }
77
+
78
+ if (!hasErrorState) {
79
+ missingStates.push('error');
80
+ }
81
+
82
+ // Check for empty state handling (conditional rendering when data is empty)
83
+ const hasEmptyCheck = /\.length\s*===\s*0/.test(fileContent) ||
84
+ /\.length\s*<\s*1/.test(fileContent) ||
85
+ /!.*\.length/.test(fileContent);
86
+
87
+ if (hasDataState && !hasEmptyCheck) {
88
+ missingStates.push('empty');
89
+ }
90
+
91
+ if (missingStates.length > 0) {
92
+ violations.push({
93
+ line: 1, // Could be improved with AST parsing for exact line
94
+ column: 1,
95
+ message: `Missing state handling: ${missingStates.join(', ')}`,
96
+ severity: 'warning',
97
+ rule: this.name,
98
+ suggestion: this.getSuggestion(missingStates),
99
+ });
100
+ }
101
+ }
102
+
103
+ // Pattern 4: Check for proper conditional rendering of states
104
+ if (hasLoadingState) {
105
+ const hasLoadingRender = fileContent.includes('loading') &&
106
+ (fileContent.includes('ProgressSpinner') ||
107
+ fileContent.includes('Skeleton') ||
108
+ fileContent.includes('loading={'));
109
+
110
+ if (!hasLoadingRender) {
111
+ violations.push({
112
+ line: 1,
113
+ column: 1,
114
+ message: 'Loading state defined but not rendered. Add <ProgressSpinner /> or loading indicator.',
115
+ severity: 'warning',
116
+ rule: this.name,
117
+ suggestion: 'if (loading) return <ProgressSpinner />;',
118
+ });
119
+ }
120
+ }
121
+
122
+ if (hasErrorState) {
123
+ const hasErrorRender = fileContent.includes('error') &&
124
+ (fileContent.includes('Message') ||
125
+ fileContent.includes('InlineMessage') ||
126
+ fileContent.includes('Toast'));
127
+
128
+ if (!hasErrorRender) {
129
+ violations.push({
130
+ line: 1,
131
+ column: 1,
132
+ message: 'Error state defined but not rendered. Add <Message severity="error" /> or error display.',
133
+ severity: 'warning',
134
+ rule: this.name,
135
+ suggestion: 'if (error) return <Message severity="error" text="Unable to load data. Try again." />;',
136
+ });
137
+ }
138
+ }
139
+
140
+ return violations;
141
+ },
142
+
143
+ getSuggestion(missingStates) {
144
+ const suggestions = {
145
+ loading: 'Add: const [loading, setLoading] = useState(false);',
146
+ error: 'Add: const [error, setError] = useState(null);',
147
+ empty: 'Add empty state check: if (data.length === 0) return <EmptyState />;',
148
+ };
149
+
150
+ return missingStates.map(state => suggestions[state]).join('\n');
151
+ },
152
+
153
+ autofix(fileContent, violation) {
154
+ // Basic autofix: Add state variable declarations at the beginning of the component
155
+
156
+ if (violation.message.includes('Missing state handling')) {
157
+ const missingStates = violation.message.match(/: (.+)$/)[1].split(', ');
158
+
159
+ let additions = [];
160
+
161
+ if (missingStates.includes('loading')) {
162
+ additions.push("const [loading, setLoading] = useState(false);");
163
+ }
164
+
165
+ if (missingStates.includes('error')) {
166
+ additions.push("const [error, setError] = useState(null);");
167
+ }
168
+
169
+ // Find the first function component or class component
170
+ const componentMatch = fileContent.match(/(function|const)\s+\w+.*\{/);
171
+
172
+ if (componentMatch) {
173
+ const insertPosition = componentMatch.index + componentMatch[0].length;
174
+ const indentation = ' ';
175
+
176
+ const fixedContent =
177
+ fileContent.slice(0, insertPosition) +
178
+ '\n' + additions.map(line => indentation + line).join('\n') +
179
+ fileContent.slice(insertPosition);
180
+
181
+ return {
182
+ fixed: true,
183
+ content: fixedContent,
184
+ message: `Added missing state declarations: ${missingStates.join(', ')}`,
185
+ };
186
+ }
187
+ }
188
+
189
+ return {
190
+ fixed: false,
191
+ message: 'Manual fix required. See suggestion in violation message.',
192
+ };
193
+ },
194
+ };
@@ -94,9 +94,9 @@ Agents must ensure all guidance, validation, and generated code consistently fol
94
94
 
95
95
  **Drift Validator** — Detect violations of architectural rules. Flag custom components that duplicate PrimeReact. Validate token-first approach.
96
96
 
97
- **Interaction Patterns** *(future)* — Enforce state visibility. Standardize empty/loading/error patterns. Specify keyboard + focus behavior. Ensure copy is clear and pragmatic.
97
+ **Interaction Patterns** *(Phase 6 - Active ✅)* — Enforce state completeness (loading/error/empty/disabled). Detect generic copy. Validate focus management and keyboard navigation. Ensure copy is clear and pragmatic.
98
98
 
99
- **Accessibility** *(future)* — Validate WCAG 2.1 AA compliance. Check contrast ratios. Ensure color is not the only cue. Verify keyboard navigation.
99
+ **Accessibility** *(Phase 6 - Active ✅)* — Validate WCAG 2.1 AA compliance. Check alt text and form labels. Verify contrast ratios. Ensure color is not the only cue. Validate ARIA and keyboard navigation.
100
100
 
101
101
  ### Quick reference for agents
102
102
 
@@ -164,5 +164,5 @@ Button labels: Specific actions ("Save Changes", "Delete Item"), not generic ("O
164
164
 
165
165
  ---
166
166
 
167
- **Status:** Mandatory reference for all agents
168
- **Last updated:** 2026-01-10
167
+ **Status:** Mandatory reference for all agents (6/6 active - Phase 6 complete ✅)
168
+ **Last updated:** 2026-01-11