chati-dev 3.3.2 → 4.0.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (48) hide show
  1. package/README.md +4 -4
  2. package/framework/agents/plan/ux.md +41 -1
  3. package/framework/config.yaml +18 -3
  4. package/framework/constitution.md +18 -10
  5. package/framework/context/governance.md +2 -0
  6. package/framework/context/root.md +1 -1
  7. package/framework/data/entity-registry.yaml +43 -3
  8. package/framework/domains/constitution.yaml +1 -1
  9. package/framework/domains/global.yaml +7 -3
  10. package/framework/domains/workflows/standard-flow.yaml +33 -0
  11. package/framework/i18n/en.yaml +11 -2
  12. package/framework/i18n/es.yaml +11 -2
  13. package/framework/i18n/fr.yaml +11 -2
  14. package/framework/i18n/pt.yaml +11 -2
  15. package/framework/intelligence/context-engine.md +22 -17
  16. package/framework/presets/nextjs.yaml +41 -0
  17. package/framework/presets/node-express.yaml +39 -0
  18. package/framework/presets/react-vite.yaml +37 -0
  19. package/framework/presets/supabase-fullstack.yaml +37 -0
  20. package/framework/templates/brandbook-tmpl.yaml +113 -0
  21. package/framework/templates/component-spec-tmpl.yaml +74 -0
  22. package/framework/templates/design-token-tmpl.yaml +55 -0
  23. package/framework/templates/icon-system-tmpl.yaml +95 -0
  24. package/package.json +1 -1
  25. package/scripts/bundle-framework.js +1 -1
  26. package/scripts/doctor/checks/agents.js +77 -0
  27. package/scripts/doctor/checks/constitution.js +41 -0
  28. package/scripts/doctor/checks/domain-alignment.js +58 -0
  29. package/scripts/doctor/checks/prism-layers.js +84 -0
  30. package/scripts/doctor/checks/registry.js +55 -0
  31. package/scripts/doctor/checks/schemas.js +61 -0
  32. package/scripts/doctor/fixes/reference-fix.js +100 -0
  33. package/scripts/doctor/fixes/registry-fix.js +56 -0
  34. package/scripts/doctor/index.js +212 -0
  35. package/scripts/health-check.js +8 -8
  36. package/src/autonomy/surface-criteria.js +226 -0
  37. package/src/context/bracket-tracker.js +44 -13
  38. package/src/context/domain-loader.js +22 -0
  39. package/src/context/engine.js +18 -7
  40. package/src/context/formatter.js +21 -1
  41. package/src/context/layers/l5-keywords.js +53 -0
  42. package/src/intelligence/context-status.js +20 -9
  43. package/src/intelligence/decision-engine.js +253 -0
  44. package/src/terminal/prompt-builder.js +341 -1
  45. package/src/terminal/run-agent.js +15 -0
  46. package/src/terminal/run-parallel.js +77 -5
  47. package/src/terminal/spawner.js +13 -0
  48. package/src/utils/feature-flags.js +106 -0
@@ -0,0 +1,55 @@
1
+ /**
2
+ * @fileoverview Doctor check: Entity Registry integrity.
3
+ *
4
+ * Validates entity-registry.yaml exists, parses correctly,
5
+ * entity count matches metadata, and all entity paths exist.
6
+ */
7
+
8
+ import { readFileSync, existsSync } from 'fs';
9
+ import { join } from 'path';
10
+ import yaml from 'js-yaml';
11
+
12
+ /**
13
+ * Run entity registry integrity check.
14
+ *
15
+ * @param {string} frameworkDir - Path to framework directory
16
+ * @returns {{ pass: boolean, details: string, severity: string, fixable: boolean, fixId: string|null }}
17
+ */
18
+ export function checkRegistry(frameworkDir) {
19
+ const regPath = join(frameworkDir, 'data', 'entity-registry.yaml');
20
+ if (!existsSync(regPath)) {
21
+ return { pass: false, details: 'entity-registry.yaml not found', severity: 'critical', fixable: false, fixId: null };
22
+ }
23
+
24
+ try {
25
+ const content = readFileSync(regPath, 'utf8');
26
+ const registry = yaml.load(content);
27
+
28
+ if (!registry || !registry.entities) {
29
+ return { pass: false, details: 'entity-registry.yaml has no entities section', severity: 'critical', fixable: false, fixId: null };
30
+ }
31
+
32
+ // Count all entities across all categories
33
+ let entityCount = 0;
34
+ for (const category of Object.values(registry.entities)) {
35
+ if (typeof category === 'object' && category !== null) {
36
+ entityCount += Object.keys(category).length;
37
+ }
38
+ }
39
+
40
+ const declaredCount = registry.metadata?.entity_count || 0;
41
+ if (declaredCount > 0 && entityCount !== declaredCount) {
42
+ return {
43
+ pass: false,
44
+ details: `Entity count mismatch: declared ${declaredCount}, actual ${entityCount}`,
45
+ severity: 'warning',
46
+ fixable: true,
47
+ fixId: 'registry-count',
48
+ };
49
+ }
50
+
51
+ return { pass: true, details: `${entityCount} entities registered`, severity: 'info', fixable: false, fixId: null };
52
+ } catch (err) {
53
+ return { pass: false, details: `Failed to parse: ${err.message}`, severity: 'critical', fixable: false, fixId: null };
54
+ }
55
+ }
@@ -0,0 +1,61 @@
1
+ /**
2
+ * @fileoverview Doctor check: JSON Schema validation.
3
+ *
4
+ * Validates that all expected schema files exist and contain valid JSON.
5
+ */
6
+
7
+ import { readFileSync, existsSync, readdirSync } from 'fs';
8
+ import { join } from 'path';
9
+
10
+ const EXPECTED_SCHEMAS = [
11
+ 'session.schema.json',
12
+ 'config.schema.json',
13
+ 'context.schema.json',
14
+ 'memory.schema.json',
15
+ 'task.schema.json',
16
+ ];
17
+
18
+ /**
19
+ * Run schema validation check.
20
+ *
21
+ * @param {string} frameworkDir - Path to framework directory
22
+ * @returns {{ pass: boolean, details: string, severity: string, fixable: boolean, fixId: string|null }}
23
+ */
24
+ export function checkSchemas(frameworkDir) {
25
+ const schemasDir = join(frameworkDir, 'schemas');
26
+ if (!existsSync(schemasDir)) {
27
+ return { pass: false, details: 'schemas/ directory not found', severity: 'critical', fixable: false, fixId: null };
28
+ }
29
+
30
+ const missing = [];
31
+ const invalid = [];
32
+
33
+ for (const schema of EXPECTED_SCHEMAS) {
34
+ const schemaPath = join(schemasDir, schema);
35
+ if (!existsSync(schemaPath)) {
36
+ missing.push(schema);
37
+ continue;
38
+ }
39
+ try {
40
+ JSON.parse(readFileSync(schemaPath, 'utf8'));
41
+ } catch {
42
+ invalid.push(schema);
43
+ }
44
+ }
45
+
46
+ if (missing.length > 0 || invalid.length > 0) {
47
+ const issues = [];
48
+ if (missing.length) issues.push(`missing: ${missing.join(', ')}`);
49
+ if (invalid.length) issues.push(`invalid JSON: ${invalid.join(', ')}`);
50
+ return {
51
+ pass: false,
52
+ details: issues.join('; '),
53
+ severity: missing.length > 0 ? 'critical' : 'warning',
54
+ fixable: false,
55
+ fixId: null,
56
+ };
57
+ }
58
+
59
+ const actualSchemas = readdirSync(schemasDir).filter(f => f.endsWith('.json'));
60
+ return { pass: true, details: `${actualSchemas.length} schemas valid`, severity: 'info', fixable: false, fixId: null };
61
+ }
@@ -0,0 +1,100 @@
1
+ /**
2
+ * @fileoverview Doctor fix: Broken path references.
3
+ *
4
+ * Scans entity-registry.yaml for broken path references and
5
+ * attempts to locate the correct paths.
6
+ */
7
+
8
+ import { readFileSync, writeFileSync, existsSync, readdirSync } from 'fs';
9
+ import { join, basename } from 'path';
10
+ import yaml from 'js-yaml';
11
+
12
+ /**
13
+ * Fix broken path references in entity-registry.yaml.
14
+ *
15
+ * @param {string} frameworkDir - Path to framework directory
16
+ * @returns {{ fixed: boolean, details: string, fixedPaths: string[] }}
17
+ */
18
+ export function fixBrokenReferences(frameworkDir) {
19
+ const regPath = join(frameworkDir, 'data', 'entity-registry.yaml');
20
+ if (!existsSync(regPath)) {
21
+ return { fixed: false, details: 'entity-registry.yaml not found', fixedPaths: [] };
22
+ }
23
+
24
+ try {
25
+ const content = readFileSync(regPath, 'utf8');
26
+ const registry = yaml.load(content);
27
+
28
+ if (!registry || !registry.entities) {
29
+ return { fixed: false, details: 'No entities section found', fixedPaths: [] };
30
+ }
31
+
32
+ const fixedPaths = [];
33
+ let updatedContent = content;
34
+
35
+ for (const category of Object.values(registry.entities)) {
36
+ for (const [name, entity] of Object.entries(category)) {
37
+ if (!entity.path) continue;
38
+
39
+ // Path in registry is relative to project root (e.g., "chati.dev/agents/...")
40
+ const relPath = entity.path.replace(/^chati\.dev\//, '');
41
+ const fullPath = join(frameworkDir, relPath);
42
+
43
+ if (existsSync(fullPath)) continue;
44
+
45
+ // Try to find the file by name
46
+ const fileName = basename(relPath);
47
+ const found = findFile(frameworkDir, fileName);
48
+
49
+ if (found) {
50
+ const newPath = 'chati.dev/' + found;
51
+ updatedContent = updatedContent.replace(
52
+ `path: ${entity.path}`,
53
+ `path: ${newPath}`
54
+ );
55
+ fixedPaths.push(`${name}: ${entity.path} -> ${newPath}`);
56
+ }
57
+ }
58
+ }
59
+
60
+ if (fixedPaths.length === 0) {
61
+ return { fixed: false, details: 'No broken references found', fixedPaths: [] };
62
+ }
63
+
64
+ writeFileSync(regPath, updatedContent, 'utf8');
65
+ return { fixed: true, details: `Fixed ${fixedPaths.length} path reference(s)`, fixedPaths };
66
+ } catch (err) {
67
+ return { fixed: false, details: `Fix failed: ${err.message}`, fixedPaths: [] };
68
+ }
69
+ }
70
+
71
+ /**
72
+ * Search for a file by name within the framework directory.
73
+ *
74
+ * @param {string} dir - Directory to search
75
+ * @param {string} fileName - File name to find
76
+ * @param {string} [prefix=''] - Path prefix for recursion
77
+ * @param {number} [depth=0] - Current depth
78
+ * @returns {string|null} Relative path from frameworkDir or null
79
+ */
80
+ function findFile(dir, fileName, prefix = '', depth = 0) {
81
+ if (depth > 4) return null;
82
+
83
+ try {
84
+ const entries = readdirSync(dir, { withFileTypes: true });
85
+ for (const entry of entries) {
86
+ const rel = prefix ? `${prefix}/${entry.name}` : entry.name;
87
+ if (entry.isFile() && entry.name === fileName) {
88
+ return rel;
89
+ }
90
+ if (entry.isDirectory() && !entry.name.startsWith('.') && entry.name !== 'node_modules') {
91
+ const found = findFile(join(dir, entry.name), fileName, rel, depth + 1);
92
+ if (found) return found;
93
+ }
94
+ }
95
+ } catch {
96
+ // Permission error or inaccessible directory
97
+ }
98
+
99
+ return null;
100
+ }
@@ -0,0 +1,56 @@
1
+ /**
2
+ * @fileoverview Doctor fix: Registry count correction.
3
+ *
4
+ * Auto-fixes entity_count mismatch in entity-registry.yaml
5
+ * by counting actual entities and updating metadata.
6
+ */
7
+
8
+ import { readFileSync, writeFileSync, existsSync } from 'fs';
9
+ import { join } from 'path';
10
+ import yaml from 'js-yaml';
11
+
12
+ /**
13
+ * Fix entity count mismatch in entity-registry.yaml.
14
+ *
15
+ * @param {string} frameworkDir - Path to framework directory
16
+ * @returns {{ fixed: boolean, details: string }}
17
+ */
18
+ export function fixRegistryCount(frameworkDir) {
19
+ const regPath = join(frameworkDir, 'data', 'entity-registry.yaml');
20
+ if (!existsSync(regPath)) {
21
+ return { fixed: false, details: 'entity-registry.yaml not found' };
22
+ }
23
+
24
+ try {
25
+ const content = readFileSync(regPath, 'utf8');
26
+ const registry = yaml.load(content);
27
+
28
+ if (!registry || !registry.entities) {
29
+ return { fixed: false, details: 'No entities section found' };
30
+ }
31
+
32
+ // Count actual entities
33
+ let entityCount = 0;
34
+ for (const category of Object.values(registry.entities)) {
35
+ if (typeof category === 'object' && category !== null) {
36
+ entityCount += Object.keys(category).length;
37
+ }
38
+ }
39
+
40
+ const declaredCount = registry.metadata?.entity_count || 0;
41
+ if (entityCount === declaredCount) {
42
+ return { fixed: false, details: 'Entity count already correct' };
43
+ }
44
+
45
+ // Update the count using regex to preserve YAML formatting
46
+ const updatedContent = content.replace(
47
+ /entity_count:\s*\d+/,
48
+ `entity_count: ${entityCount}`
49
+ );
50
+
51
+ writeFileSync(regPath, updatedContent, 'utf8');
52
+ return { fixed: true, details: `Updated entity_count from ${declaredCount} to ${entityCount}` };
53
+ } catch (err) {
54
+ return { fixed: false, details: `Fix failed: ${err.message}` };
55
+ }
56
+ }
@@ -0,0 +1,212 @@
1
+ #!/usr/bin/env node
2
+
3
+ /**
4
+ * @fileoverview Doctor — Modular diagnostic system with auto-fix capability.
5
+ *
6
+ * Orchestrates individual check modules and fix modules to provide
7
+ * comprehensive framework health validation and automatic repair.
8
+ *
9
+ * Exports:
10
+ * runDoctor(frameworkDir, options) → DoctorReport
11
+ * formatDoctorReport(report) → human-readable string
12
+ *
13
+ * DoctorReport:
14
+ * { overall, checks, fixes, timestamp, summary }
15
+ */
16
+
17
+ import { existsSync } from 'fs';
18
+ import { join } from 'path';
19
+ import { fileURLToPath } from 'url';
20
+
21
+ // Check modules
22
+ import { checkRegistry } from './checks/registry.js';
23
+ import { checkSchemas } from './checks/schemas.js';
24
+ import { checkConstitution } from './checks/constitution.js';
25
+ import { checkAgents } from './checks/agents.js';
26
+ import { checkPrismLayers } from './checks/prism-layers.js';
27
+ import { checkDomainAlignment } from './checks/domain-alignment.js';
28
+
29
+ // Fix modules
30
+ import { fixRegistryCount } from './fixes/registry-fix.js';
31
+ import { fixBrokenReferences } from './fixes/reference-fix.js';
32
+
33
+ /**
34
+ * Map of fixId to fix function.
35
+ */
36
+ const FIX_REGISTRY = {
37
+ 'registry-count': fixRegistryCount,
38
+ 'broken-references': fixBrokenReferences,
39
+ };
40
+
41
+ /**
42
+ * All available check functions.
43
+ */
44
+ const CHECK_REGISTRY = {
45
+ registry: checkRegistry,
46
+ schemas: checkSchemas,
47
+ constitution: checkConstitution,
48
+ agents: checkAgents,
49
+ prismLayers: checkPrismLayers,
50
+ domainAlignment: checkDomainAlignment,
51
+ };
52
+
53
+ /**
54
+ * Run the doctor diagnostic suite.
55
+ *
56
+ * @param {string} frameworkDir - Path to the framework directory
57
+ * @param {object} [options={}] - Configuration options
58
+ * @param {boolean} [options.fix=false] - Run auto-fix for fixable issues
59
+ * @param {string[]} [options.checks] - Specific checks to run (default: all)
60
+ * @returns {object} DoctorReport
61
+ */
62
+ export function runDoctor(frameworkDir, options = {}) {
63
+ const { fix = false, checks: checkFilter } = options;
64
+
65
+ if (!existsSync(frameworkDir)) {
66
+ return {
67
+ overall: 'UNHEALTHY',
68
+ checks: {
69
+ frameworkDir: { pass: false, details: `Directory not found: ${frameworkDir}`, severity: 'critical', fixable: false, fixId: null },
70
+ },
71
+ fixes: [],
72
+ timestamp: new Date().toISOString(),
73
+ summary: { total: 1, passed: 0, failed: 1, criticalFailures: 1, warnings: 0, fixesApplied: 0 },
74
+ };
75
+ }
76
+
77
+ // Run checks
78
+ const checksToRun = checkFilter
79
+ ? Object.entries(CHECK_REGISTRY).filter(([name]) => checkFilter.includes(name))
80
+ : Object.entries(CHECK_REGISTRY);
81
+
82
+ const checkResults = {};
83
+ for (const [name, checkFn] of checksToRun) {
84
+ checkResults[name] = checkFn(frameworkDir);
85
+ }
86
+
87
+ // Run fixes if requested
88
+ const fixes = [];
89
+ if (fix) {
90
+ for (const [name, result] of Object.entries(checkResults)) {
91
+ if (!result.pass && result.fixable && result.fixId) {
92
+ const fixFn = FIX_REGISTRY[result.fixId];
93
+ if (fixFn) {
94
+ const fixResult = fixFn(frameworkDir);
95
+ fixes.push({ check: name, fixId: result.fixId, ...fixResult });
96
+
97
+ // Re-run the check if fix was applied
98
+ if (fixResult.fixed) {
99
+ const checkFn = CHECK_REGISTRY[name];
100
+ if (checkFn) {
101
+ checkResults[name] = checkFn(frameworkDir);
102
+ }
103
+ }
104
+ }
105
+ }
106
+ }
107
+ }
108
+
109
+ // Determine overall health
110
+ const criticalFailures = Object.values(checkResults).filter(c => !c.pass && c.severity === 'critical');
111
+ const warnings = Object.values(checkResults).filter(c => !c.pass && c.severity === 'warning');
112
+
113
+ let overall;
114
+ if (criticalFailures.length > 0) {
115
+ overall = 'UNHEALTHY';
116
+ } else if (warnings.length > 0) {
117
+ overall = 'DEGRADED';
118
+ } else {
119
+ overall = 'HEALTHY';
120
+ }
121
+
122
+ return {
123
+ overall,
124
+ checks: checkResults,
125
+ fixes,
126
+ timestamp: new Date().toISOString(),
127
+ summary: {
128
+ total: Object.keys(checkResults).length,
129
+ passed: Object.values(checkResults).filter(c => c.pass).length,
130
+ failed: Object.values(checkResults).filter(c => !c.pass).length,
131
+ criticalFailures: criticalFailures.length,
132
+ warnings: warnings.length,
133
+ fixesApplied: fixes.filter(f => f.fixed).length,
134
+ },
135
+ };
136
+ }
137
+
138
+ /**
139
+ * Format a DoctorReport into a human-readable string.
140
+ *
141
+ * @param {object} report - DoctorReport from runDoctor
142
+ * @returns {string} Formatted report
143
+ */
144
+ export function formatDoctorReport(report) {
145
+ const statusSymbol = {
146
+ HEALTHY: '[OK]',
147
+ DEGRADED: '[WARN]',
148
+ UNHEALTHY: '[FAIL]',
149
+ };
150
+
151
+ const checkSymbol = (check) => check.pass ? '[PASS]' : '[FAIL]';
152
+
153
+ const lines = [
154
+ '=== Doctor Report ===',
155
+ `Status: ${statusSymbol[report.overall] || '[ ? ]'} ${report.overall}`,
156
+ `Time: ${report.timestamp}`,
157
+ '',
158
+ ];
159
+
160
+ if (report.summary) {
161
+ lines.push(`Summary: ${report.summary.passed}/${report.summary.total} checks passed`);
162
+ if (report.summary.criticalFailures > 0) {
163
+ lines.push(` ${report.summary.criticalFailures} critical failure(s)`);
164
+ }
165
+ if (report.summary.warnings > 0) {
166
+ lines.push(` ${report.summary.warnings} warning(s)`);
167
+ }
168
+ if (report.summary.fixesApplied > 0) {
169
+ lines.push(` ${report.summary.fixesApplied} fix(es) applied`);
170
+ }
171
+ lines.push('');
172
+ }
173
+
174
+ lines.push('Checks:');
175
+ for (const [name, check] of Object.entries(report.checks)) {
176
+ const symbol = checkSymbol(check);
177
+ const severity = check.severity !== 'info' ? ` (${check.severity})` : '';
178
+ const fixable = check.fixable ? ' [fixable]' : '';
179
+ lines.push(` ${symbol} ${name}: ${check.details}${severity}${fixable}`);
180
+ }
181
+
182
+ if (report.fixes && report.fixes.length > 0) {
183
+ lines.push('');
184
+ lines.push('Fixes:');
185
+ for (const fix of report.fixes) {
186
+ const symbol = fix.fixed ? '[FIXED]' : '[SKIP]';
187
+ lines.push(` ${symbol} ${fix.check}: ${fix.details}`);
188
+ }
189
+ }
190
+
191
+ lines.push('');
192
+ lines.push('=== End Report ===');
193
+
194
+ return lines.join('\n');
195
+ }
196
+
197
+ // ---------------------------------------------------------------------------
198
+ // CLI entrypoint
199
+ // ---------------------------------------------------------------------------
200
+
201
+ if (process.argv[1] === fileURLToPath(import.meta.url)) {
202
+ const args = process.argv.slice(2);
203
+ const fix = args.includes('--fix');
204
+ const frameworkDir = args.find(a => !a.startsWith('--')) || join(process.cwd(), 'chati.dev');
205
+
206
+ const report = runDoctor(frameworkDir, { fix });
207
+ console.log(formatDoctorReport(report));
208
+
209
+ if (report.overall === 'UNHEALTHY') {
210
+ process.exit(1);
211
+ }
212
+ }
@@ -146,14 +146,14 @@ function checkAgents(frameworkDir) {
146
146
  const orchestratorDir = join(frameworkDir, 'orchestrator');
147
147
 
148
148
  const expectedAgents = {
149
- 'planning/greenfield-wu.md': 'greenfield-wu',
150
- 'planning/brownfield-wu.md': 'brownfield-wu',
151
- 'planning/brief.md': 'brief',
152
- 'planning/detail.md': 'detail',
153
- 'planning/architect.md': 'architect',
154
- 'planning/ux.md': 'ux',
155
- 'planning/phases.md': 'phases',
156
- 'planning/tasks.md': 'tasks',
149
+ 'discover/greenfield-wu.md': 'greenfield-wu',
150
+ 'discover/brownfield-wu.md': 'brownfield-wu',
151
+ 'discover/brief.md': 'brief',
152
+ 'plan/detail.md': 'detail',
153
+ 'plan/architect.md': 'architect',
154
+ 'plan/ux.md': 'ux',
155
+ 'plan/phases.md': 'phases',
156
+ 'plan/tasks.md': 'tasks',
157
157
  'quality/qa-planning.md': 'qa-planning',
158
158
  'quality/qa-implementation.md': 'qa-implementation',
159
159
  'build/dev.md': 'dev',