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.
- package/README.md +4 -4
- package/framework/agents/plan/ux.md +41 -1
- package/framework/config.yaml +18 -3
- package/framework/constitution.md +18 -10
- package/framework/context/governance.md +2 -0
- package/framework/context/root.md +1 -1
- package/framework/data/entity-registry.yaml +43 -3
- package/framework/domains/constitution.yaml +1 -1
- package/framework/domains/global.yaml +7 -3
- package/framework/domains/workflows/standard-flow.yaml +33 -0
- package/framework/i18n/en.yaml +11 -2
- package/framework/i18n/es.yaml +11 -2
- package/framework/i18n/fr.yaml +11 -2
- package/framework/i18n/pt.yaml +11 -2
- package/framework/intelligence/context-engine.md +22 -17
- package/framework/presets/nextjs.yaml +41 -0
- package/framework/presets/node-express.yaml +39 -0
- package/framework/presets/react-vite.yaml +37 -0
- package/framework/presets/supabase-fullstack.yaml +37 -0
- package/framework/templates/brandbook-tmpl.yaml +113 -0
- package/framework/templates/component-spec-tmpl.yaml +74 -0
- package/framework/templates/design-token-tmpl.yaml +55 -0
- package/framework/templates/icon-system-tmpl.yaml +95 -0
- package/package.json +1 -1
- package/scripts/bundle-framework.js +1 -1
- package/scripts/doctor/checks/agents.js +77 -0
- package/scripts/doctor/checks/constitution.js +41 -0
- package/scripts/doctor/checks/domain-alignment.js +58 -0
- package/scripts/doctor/checks/prism-layers.js +84 -0
- package/scripts/doctor/checks/registry.js +55 -0
- package/scripts/doctor/checks/schemas.js +61 -0
- package/scripts/doctor/fixes/reference-fix.js +100 -0
- package/scripts/doctor/fixes/registry-fix.js +56 -0
- package/scripts/doctor/index.js +212 -0
- package/scripts/health-check.js +8 -8
- package/src/autonomy/surface-criteria.js +226 -0
- package/src/context/bracket-tracker.js +44 -13
- package/src/context/domain-loader.js +22 -0
- package/src/context/engine.js +18 -7
- package/src/context/formatter.js +21 -1
- package/src/context/layers/l5-keywords.js +53 -0
- package/src/intelligence/context-status.js +20 -9
- package/src/intelligence/decision-engine.js +253 -0
- package/src/terminal/prompt-builder.js +341 -1
- package/src/terminal/run-agent.js +15 -0
- package/src/terminal/run-parallel.js +77 -5
- package/src/terminal/spawner.js +13 -0
- 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
|
+
}
|
package/scripts/health-check.js
CHANGED
|
@@ -146,14 +146,14 @@ function checkAgents(frameworkDir) {
|
|
|
146
146
|
const orchestratorDir = join(frameworkDir, 'orchestrator');
|
|
147
147
|
|
|
148
148
|
const expectedAgents = {
|
|
149
|
-
'
|
|
150
|
-
'
|
|
151
|
-
'
|
|
152
|
-
'
|
|
153
|
-
'
|
|
154
|
-
'
|
|
155
|
-
'
|
|
156
|
-
'
|
|
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',
|