chati-dev 1.0.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/assets/logo.txt +6 -0
- package/bin/chati.js +175 -0
- package/framework/agents/build/dev.md +342 -0
- package/framework/agents/clarity/architect.md +263 -0
- package/framework/agents/clarity/brief.md +277 -0
- package/framework/agents/clarity/brownfield-wu.md +288 -0
- package/framework/agents/clarity/detail.md +274 -0
- package/framework/agents/clarity/greenfield-wu.md +231 -0
- package/framework/agents/clarity/phases.md +272 -0
- package/framework/agents/clarity/tasks.md +279 -0
- package/framework/agents/clarity/ux.md +293 -0
- package/framework/agents/deploy/devops.md +321 -0
- package/framework/agents/quality/qa-implementation.md +310 -0
- package/framework/agents/quality/qa-planning.md +289 -0
- package/framework/config.yaml +8 -0
- package/framework/constitution.md +238 -0
- package/framework/frameworks/decision-heuristics.yaml +64 -0
- package/framework/frameworks/quality-dimensions.yaml +59 -0
- package/framework/i18n/en.yaml +78 -0
- package/framework/i18n/es.yaml +78 -0
- package/framework/i18n/fr.yaml +78 -0
- package/framework/i18n/pt.yaml +78 -0
- package/framework/intelligence/confidence.yaml +42 -0
- package/framework/intelligence/gotchas.yaml +51 -0
- package/framework/intelligence/patterns.yaml +32 -0
- package/framework/migrations/v1.0-to-v1.1.yaml +48 -0
- package/framework/orchestrator/chati.md +333 -0
- package/framework/patterns/elicitation.md +137 -0
- package/framework/quality-gates/implementation-gate.md +64 -0
- package/framework/quality-gates/planning-gate.md +52 -0
- package/framework/schemas/config.schema.json +42 -0
- package/framework/schemas/session.schema.json +103 -0
- package/framework/schemas/task.schema.json +71 -0
- package/framework/templates/brownfield-prd-tmpl.yaml +103 -0
- package/framework/templates/fullstack-architecture-tmpl.yaml +101 -0
- package/framework/templates/prd-tmpl.yaml +94 -0
- package/framework/templates/qa-gate-tmpl.yaml +96 -0
- package/framework/templates/task-tmpl.yaml +85 -0
- package/framework/workflows/brownfield-discovery.yaml +75 -0
- package/framework/workflows/brownfield-fullstack.yaml +104 -0
- package/framework/workflows/brownfield-service.yaml +81 -0
- package/framework/workflows/brownfield-ui.yaml +87 -0
- package/framework/workflows/greenfield-fullstack.yaml +108 -0
- package/package.json +60 -0
- package/scripts/bundle-framework.js +58 -0
- package/src/config/ide-configs.js +80 -0
- package/src/config/mcp-configs.js +136 -0
- package/src/dashboard/data-reader.js +99 -0
- package/src/dashboard/layout.js +161 -0
- package/src/dashboard/renderer.js +104 -0
- package/src/installer/core.js +221 -0
- package/src/installer/templates.js +97 -0
- package/src/installer/validator.js +114 -0
- package/src/upgrade/backup.js +107 -0
- package/src/upgrade/checker.js +105 -0
- package/src/upgrade/migrator.js +171 -0
- package/src/utils/colors.js +18 -0
- package/src/utils/detector.js +51 -0
- package/src/utils/logger.js +41 -0
- package/src/wizard/feedback.js +76 -0
- package/src/wizard/i18n.js +168 -0
- package/src/wizard/index.js +107 -0
- package/src/wizard/questions.js +169 -0
|
@@ -0,0 +1,136 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* MCP Registry and Configuration
|
|
3
|
+
* Defines available MCPs and their agent dependencies
|
|
4
|
+
*/
|
|
5
|
+
export const MCP_CONFIGS = {
|
|
6
|
+
browser: {
|
|
7
|
+
name: 'Browser (Playwright)',
|
|
8
|
+
description: 'Web automation and testing',
|
|
9
|
+
defaultSelected: true,
|
|
10
|
+
requiresEnv: [],
|
|
11
|
+
claudeConfig: {
|
|
12
|
+
command: 'npx',
|
|
13
|
+
args: ['-y', '@playwright/mcp'],
|
|
14
|
+
},
|
|
15
|
+
},
|
|
16
|
+
context7: {
|
|
17
|
+
name: 'Context7',
|
|
18
|
+
description: 'Library documentation search',
|
|
19
|
+
defaultSelected: true,
|
|
20
|
+
requiresEnv: [],
|
|
21
|
+
claudeConfig: {
|
|
22
|
+
command: 'npx',
|
|
23
|
+
args: ['-y', '@context7/mcp'],
|
|
24
|
+
},
|
|
25
|
+
},
|
|
26
|
+
exa: {
|
|
27
|
+
name: 'Exa',
|
|
28
|
+
description: 'Advanced web search',
|
|
29
|
+
defaultSelected: true,
|
|
30
|
+
requiresEnv: ['EXA_API_KEY'],
|
|
31
|
+
claudeConfig: {
|
|
32
|
+
command: 'npx',
|
|
33
|
+
args: ['-y', '@exa/mcp'],
|
|
34
|
+
env: { EXA_API_KEY: '${EXA_API_KEY}' },
|
|
35
|
+
},
|
|
36
|
+
},
|
|
37
|
+
'desktop-commander': {
|
|
38
|
+
name: 'Desktop Commander',
|
|
39
|
+
description: 'File system access',
|
|
40
|
+
defaultSelected: false,
|
|
41
|
+
requiresEnv: [],
|
|
42
|
+
claudeConfig: {
|
|
43
|
+
command: 'npx',
|
|
44
|
+
args: ['-y', '@anthropic/desktop-commander-mcp'],
|
|
45
|
+
},
|
|
46
|
+
},
|
|
47
|
+
'sequential-thinking': {
|
|
48
|
+
name: 'Sequential Thinking',
|
|
49
|
+
description: 'Step-by-step reasoning',
|
|
50
|
+
defaultSelected: false,
|
|
51
|
+
requiresEnv: [],
|
|
52
|
+
claudeConfig: {
|
|
53
|
+
command: 'npx',
|
|
54
|
+
args: ['-y', '@anthropic/sequential-thinking-mcp'],
|
|
55
|
+
},
|
|
56
|
+
},
|
|
57
|
+
github: {
|
|
58
|
+
name: 'GitHub',
|
|
59
|
+
description: 'GitHub API integration',
|
|
60
|
+
defaultSelected: false,
|
|
61
|
+
requiresEnv: ['GITHUB_TOKEN'],
|
|
62
|
+
claudeConfig: {
|
|
63
|
+
command: 'npx',
|
|
64
|
+
args: ['-y', '@anthropic/github-mcp'],
|
|
65
|
+
env: { GITHUB_TOKEN: '${GITHUB_TOKEN}' },
|
|
66
|
+
},
|
|
67
|
+
},
|
|
68
|
+
};
|
|
69
|
+
|
|
70
|
+
/**
|
|
71
|
+
* Agent-MCP dependency matrix
|
|
72
|
+
*/
|
|
73
|
+
export const AGENT_MCP_DEPS = {
|
|
74
|
+
orchestrator: { required: [], optional: ['git'] },
|
|
75
|
+
'greenfield-wu': { required: [], optional: [] },
|
|
76
|
+
'brownfield-wu': { required: ['git'], optional: ['browser'] },
|
|
77
|
+
brief: { required: [], optional: [] },
|
|
78
|
+
detail: { required: [], optional: ['exa'] },
|
|
79
|
+
architect: { required: ['context7'], optional: ['exa', 'git'] },
|
|
80
|
+
ux: { required: [], optional: ['browser'] },
|
|
81
|
+
phases: { required: [], optional: [] },
|
|
82
|
+
tasks: { required: [], optional: [] },
|
|
83
|
+
'qa-planning': { required: [], optional: [] },
|
|
84
|
+
dev: { required: ['context7', 'git'], optional: ['browser', 'coderabbit'] },
|
|
85
|
+
'qa-implementation':{ required: ['git'], optional: ['browser', 'coderabbit'] },
|
|
86
|
+
devops: { required: ['git', 'github'], optional: [] },
|
|
87
|
+
};
|
|
88
|
+
|
|
89
|
+
/**
|
|
90
|
+
* Get list of MCPs for selection prompt
|
|
91
|
+
*/
|
|
92
|
+
export function getMCPChoices() {
|
|
93
|
+
return Object.entries(MCP_CONFIGS).map(([key, config]) => ({
|
|
94
|
+
value: key,
|
|
95
|
+
label: config.name,
|
|
96
|
+
hint: config.description,
|
|
97
|
+
initialValue: config.defaultSelected,
|
|
98
|
+
}));
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
/**
|
|
102
|
+
* Check MCP warnings for selected project type and MCPs
|
|
103
|
+
*/
|
|
104
|
+
export function getMCPWarnings(projectType, selectedMCPs) {
|
|
105
|
+
const warnings = [];
|
|
106
|
+
|
|
107
|
+
if (projectType === 'brownfield' && !selectedMCPs.includes('git')) {
|
|
108
|
+
warnings.push({
|
|
109
|
+
level: 'critical',
|
|
110
|
+
message: "You selected Brownfield but did not enable 'git' MCP. The brownfield-wu agent requires git for codebase analysis.",
|
|
111
|
+
});
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
return warnings;
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
/**
|
|
118
|
+
* Generate MCP config for Claude Code (.claude/mcp.json)
|
|
119
|
+
*/
|
|
120
|
+
export function generateClaudeMCPConfig(selectedMCPs) {
|
|
121
|
+
const mcpServers = {};
|
|
122
|
+
for (const mcpKey of selectedMCPs) {
|
|
123
|
+
const config = MCP_CONFIGS[mcpKey];
|
|
124
|
+
if (config?.claudeConfig) {
|
|
125
|
+
const entry = {
|
|
126
|
+
command: config.claudeConfig.command,
|
|
127
|
+
args: config.claudeConfig.args,
|
|
128
|
+
};
|
|
129
|
+
if (config.claudeConfig.env) {
|
|
130
|
+
entry.env = config.claudeConfig.env;
|
|
131
|
+
}
|
|
132
|
+
mcpServers[mcpKey] = entry;
|
|
133
|
+
}
|
|
134
|
+
}
|
|
135
|
+
return { mcpServers };
|
|
136
|
+
}
|
|
@@ -0,0 +1,99 @@
|
|
|
1
|
+
import { readFileSync, existsSync, readdirSync, statSync } from 'fs';
|
|
2
|
+
import { join } from 'path';
|
|
3
|
+
import yaml from 'js-yaml';
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* Read all dashboard data from project directory
|
|
7
|
+
*/
|
|
8
|
+
export function readDashboardData(targetDir) {
|
|
9
|
+
const data = {
|
|
10
|
+
session: null,
|
|
11
|
+
config: null,
|
|
12
|
+
agentScores: {},
|
|
13
|
+
taskProgress: null,
|
|
14
|
+
validationResults: null,
|
|
15
|
+
recentActivity: [],
|
|
16
|
+
blockers: [],
|
|
17
|
+
gotchas: [],
|
|
18
|
+
};
|
|
19
|
+
|
|
20
|
+
// Read session.yaml
|
|
21
|
+
const sessionPath = join(targetDir, '.chati', 'session.yaml');
|
|
22
|
+
if (existsSync(sessionPath)) {
|
|
23
|
+
try {
|
|
24
|
+
data.session = yaml.load(readFileSync(sessionPath, 'utf-8'));
|
|
25
|
+
} catch {
|
|
26
|
+
data.session = null;
|
|
27
|
+
}
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
// Read config.yaml
|
|
31
|
+
const configPath = join(targetDir, 'chati.dev', 'config.yaml');
|
|
32
|
+
if (existsSync(configPath)) {
|
|
33
|
+
try {
|
|
34
|
+
data.config = yaml.load(readFileSync(configPath, 'utf-8'));
|
|
35
|
+
} catch {
|
|
36
|
+
data.config = null;
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
// Extract agent scores
|
|
41
|
+
if (data.session?.agents) {
|
|
42
|
+
for (const [name, info] of Object.entries(data.session.agents)) {
|
|
43
|
+
data.agentScores[name] = {
|
|
44
|
+
status: info.status || 'pending',
|
|
45
|
+
score: info.score || 0,
|
|
46
|
+
completedAt: info.completed_at || null,
|
|
47
|
+
};
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
// Extract blockers from backlog (high priority items)
|
|
52
|
+
if (data.session?.backlog) {
|
|
53
|
+
data.blockers = data.session.backlog.filter(
|
|
54
|
+
item => item.priority === 'high' && item.status !== 'done'
|
|
55
|
+
);
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
// Read task progress from artifacts
|
|
59
|
+
const tasksDir = join(targetDir, 'chati.dev', 'artifacts', '6-Tasks');
|
|
60
|
+
if (existsSync(tasksDir)) {
|
|
61
|
+
try {
|
|
62
|
+
const files = readdirSync(tasksDir).filter(f => f.endsWith('.yaml') || f.endsWith('.md'));
|
|
63
|
+
data.taskProgress = { totalFiles: files.length };
|
|
64
|
+
} catch {
|
|
65
|
+
// Ignore
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
// Read gotchas
|
|
70
|
+
const gotchasPath = join(targetDir, 'chati.dev', 'intelligence', 'gotchas.yaml');
|
|
71
|
+
if (existsSync(gotchasPath)) {
|
|
72
|
+
try {
|
|
73
|
+
const gotchasData = yaml.load(readFileSync(gotchasPath, 'utf-8'));
|
|
74
|
+
data.gotchas = gotchasData?.gotchas || [];
|
|
75
|
+
} catch {
|
|
76
|
+
// Ignore
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
// Build recent activity from agent completion timestamps
|
|
81
|
+
if (data.session?.agents) {
|
|
82
|
+
for (const [name, info] of Object.entries(data.session.agents)) {
|
|
83
|
+
if (info.completed_at) {
|
|
84
|
+
data.recentActivity.push({
|
|
85
|
+
agent: name,
|
|
86
|
+
score: info.score,
|
|
87
|
+
completedAt: info.completed_at,
|
|
88
|
+
});
|
|
89
|
+
}
|
|
90
|
+
}
|
|
91
|
+
data.recentActivity.sort((a, b) => {
|
|
92
|
+
if (!a.completedAt) return 1;
|
|
93
|
+
if (!b.completedAt) return -1;
|
|
94
|
+
return new Date(b.completedAt) - new Date(a.completedAt);
|
|
95
|
+
});
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
return data;
|
|
99
|
+
}
|
|
@@ -0,0 +1,161 @@
|
|
|
1
|
+
import { brand, dim, success, yellow, green, gray, bold, red } from '../utils/colors.js';
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Dashboard layout definition
|
|
5
|
+
* Formats data into the TUI display
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
/**
|
|
9
|
+
* Format agent score with color
|
|
10
|
+
*/
|
|
11
|
+
function formatScore(score, status) {
|
|
12
|
+
if (status === 'pending') return gray('--');
|
|
13
|
+
if (status === 'in_progress') return yellow('...');
|
|
14
|
+
if (score >= 95) return green(String(score));
|
|
15
|
+
if (score >= 80) return yellow(String(score));
|
|
16
|
+
return red(String(score));
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
/**
|
|
20
|
+
* Format project state with label
|
|
21
|
+
*/
|
|
22
|
+
function formatState(state) {
|
|
23
|
+
const stateMap = {
|
|
24
|
+
clarity: brand('CLARITY'),
|
|
25
|
+
build: yellow('BUILD'),
|
|
26
|
+
validate: bold('VALIDATE'),
|
|
27
|
+
deploy: green('DEPLOY'),
|
|
28
|
+
completed: success('COMPLETED'),
|
|
29
|
+
};
|
|
30
|
+
return stateMap[state] || gray(state || 'unknown');
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
/**
|
|
34
|
+
* Format execution mode
|
|
35
|
+
*/
|
|
36
|
+
function formatMode(mode) {
|
|
37
|
+
if (mode === 'autonomous') return yellow('Ralph Wiggum');
|
|
38
|
+
return dim('Interactive');
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
/**
|
|
42
|
+
* Build the header section
|
|
43
|
+
*/
|
|
44
|
+
export function buildHeader(data) {
|
|
45
|
+
const version = data.config?.version || '?.?.?';
|
|
46
|
+
const line = '─'.repeat(59);
|
|
47
|
+
return [
|
|
48
|
+
brand(`┌${line}┐`),
|
|
49
|
+
brand(`│ chati.dev Dashboard`) + ' '.repeat(59 - 21 - version.length - 2) + dim(`v${version}`) + brand(' │'),
|
|
50
|
+
brand(`├${line}┤`),
|
|
51
|
+
];
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
/**
|
|
55
|
+
* Build project info section
|
|
56
|
+
*/
|
|
57
|
+
export function buildProjectInfo(data) {
|
|
58
|
+
const session = data.session || {};
|
|
59
|
+
const project = session.project || {};
|
|
60
|
+
|
|
61
|
+
const name = project.name || 'unknown';
|
|
62
|
+
const type = project.type === 'brownfield' ? 'Brownfield' : 'Greenfield';
|
|
63
|
+
const state = formatState(project.state);
|
|
64
|
+
const mode = formatMode(session.execution_mode);
|
|
65
|
+
const lang = session.language || 'en';
|
|
66
|
+
const ide = (session.ides || [])[0] || 'unknown';
|
|
67
|
+
|
|
68
|
+
return [
|
|
69
|
+
brand('│') + ` ${dim('Project:')} ${name}` + ' '.repeat(Math.max(1, 30 - name.length)) + `${dim('Type:')} ${type}` + ' '.repeat(Math.max(1, 18 - type.length)) + brand('│'),
|
|
70
|
+
brand('│') + ` ${dim('Phase:')} ${state}` + ' '.repeat(Math.max(1, 28)) + `${dim('Mode:')} ${mode}` + ' '.repeat(Math.max(1, 10)) + brand('│'),
|
|
71
|
+
brand('│') + ` ${dim('Language:')} ${lang}` + ' '.repeat(Math.max(1, 30 - lang.length)) + `${dim('IDE:')} ${ide}` + ' '.repeat(Math.max(1, 18 - ide.length)) + brand('│'),
|
|
72
|
+
];
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
/**
|
|
76
|
+
* Build CLARITY section with agent scores
|
|
77
|
+
*/
|
|
78
|
+
export function buildClaritySection(data) {
|
|
79
|
+
const scores = data.agentScores || {};
|
|
80
|
+
|
|
81
|
+
const clarityAgents = [
|
|
82
|
+
['WU', scores['greenfield-wu'] || scores['brownfield-wu'] || { status: 'pending', score: 0 }],
|
|
83
|
+
['Brief', scores['brief'] || { status: 'pending', score: 0 }],
|
|
84
|
+
['Detail', scores['detail'] || { status: 'pending', score: 0 }],
|
|
85
|
+
['Arch', scores['architect'] || { status: 'pending', score: 0 }],
|
|
86
|
+
['UX', scores['ux'] || { status: 'pending', score: 0 }],
|
|
87
|
+
['Phases', scores['phases'] || { status: 'pending', score: 0 }],
|
|
88
|
+
['Tasks', scores['tasks'] || { status: 'pending', score: 0 }],
|
|
89
|
+
['QA-P', scores['qa-planning'] || { status: 'pending', score: 0 }],
|
|
90
|
+
];
|
|
91
|
+
|
|
92
|
+
const row1 = clarityAgents.slice(0, 4)
|
|
93
|
+
.map(([name, info]) => `${dim(name.padEnd(8))} ${formatScore(info.score, info.status)}`)
|
|
94
|
+
.join(' ');
|
|
95
|
+
|
|
96
|
+
const row2 = clarityAgents.slice(4)
|
|
97
|
+
.map(([name, info]) => `${dim(name.padEnd(8))} ${formatScore(info.score, info.status)}`)
|
|
98
|
+
.join(' ');
|
|
99
|
+
|
|
100
|
+
return [
|
|
101
|
+
brand('│') + ` ${brand('── CLARITY')} ${'─'.repeat(46)}` + brand('│'),
|
|
102
|
+
brand('│') + ` │ ${row1}` + ' '.repeat(5) + brand('│'),
|
|
103
|
+
brand('│') + ` │ ${row2}` + ' '.repeat(5) + brand('│'),
|
|
104
|
+
brand('│') + ` ${'─'.repeat(57)}` + brand('│'),
|
|
105
|
+
];
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
/**
|
|
109
|
+
* Build BUILD section
|
|
110
|
+
*/
|
|
111
|
+
export function buildBuildSection(data) {
|
|
112
|
+
const devInfo = data.agentScores?.dev || { status: 'pending', score: 0 };
|
|
113
|
+
const status = devInfo.status === 'pending' ? gray('Waiting') :
|
|
114
|
+
devInfo.status === 'in_progress' ? yellow('In Progress') :
|
|
115
|
+
green('Complete');
|
|
116
|
+
|
|
117
|
+
return [
|
|
118
|
+
brand('│') + ` ${brand('── BUILD')} ${'─'.repeat(48)}` + brand('│'),
|
|
119
|
+
brand('│') + ` │ ${dim('Dev Agent:')} ${status}` + ' '.repeat(38) + brand('│'),
|
|
120
|
+
brand('│') + ` ${'─'.repeat(57)}` + brand('│'),
|
|
121
|
+
];
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
/**
|
|
125
|
+
* Build VALIDATE section
|
|
126
|
+
*/
|
|
127
|
+
export function buildValidateSection(data) {
|
|
128
|
+
const qaInfo = data.agentScores?.['qa-implementation'] || { status: 'pending', score: 0 };
|
|
129
|
+
const tests = qaInfo.status === 'completed' ? green('Pass') : gray('--');
|
|
130
|
+
|
|
131
|
+
return [
|
|
132
|
+
brand('│') + ` ${brand('── VALIDATE')} ${'─'.repeat(45)}` + brand('│'),
|
|
133
|
+
brand('│') + ` │ ${dim('Tests:')} ${tests} ${dim('SAST:')} ${gray('--')} ${dim('DAST:')} ${gray('--')} ${dim('DS Audit:')} ${gray('--')}` + ' '.repeat(5) + brand('│'),
|
|
134
|
+
brand('│') + ` ${'─'.repeat(57)}` + brand('│'),
|
|
135
|
+
];
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
/**
|
|
139
|
+
* Build footer with recent activity, blockers, gotchas
|
|
140
|
+
*/
|
|
141
|
+
export function buildFooter(data) {
|
|
142
|
+
const recent = data.recentActivity?.[0];
|
|
143
|
+
const recentText = recent
|
|
144
|
+
? `${recent.agent} completed (score: ${recent.score})`
|
|
145
|
+
: 'No recent activity';
|
|
146
|
+
|
|
147
|
+
const blockerCount = data.blockers?.length || 0;
|
|
148
|
+
const blockerText = blockerCount > 0 ? red(`${blockerCount} active`) : green('None');
|
|
149
|
+
|
|
150
|
+
const gotchaCount = data.gotchas?.length || 0;
|
|
151
|
+
const gotchaText = gotchaCount > 0 ? `${gotchaCount} patterns learned` : 'None yet';
|
|
152
|
+
|
|
153
|
+
const line = '─'.repeat(59);
|
|
154
|
+
|
|
155
|
+
return [
|
|
156
|
+
brand('│') + ` ${dim('Recent:')} ${recentText}` + ' '.repeat(Math.max(1, 40 - recentText.length)) + brand('│'),
|
|
157
|
+
brand('│') + ` ${dim('Blockers:')} ${blockerText}` + ' '.repeat(42) + brand('│'),
|
|
158
|
+
brand('│') + ` ${dim('Gotchas:')} ${gotchaText}` + ' '.repeat(Math.max(1, 40 - gotchaText.length)) + brand('│'),
|
|
159
|
+
brand(`└${line}┘`),
|
|
160
|
+
];
|
|
161
|
+
}
|
|
@@ -0,0 +1,104 @@
|
|
|
1
|
+
import { readDashboardData } from './data-reader.js';
|
|
2
|
+
import {
|
|
3
|
+
buildHeader,
|
|
4
|
+
buildProjectInfo,
|
|
5
|
+
buildClaritySection,
|
|
6
|
+
buildBuildSection,
|
|
7
|
+
buildValidateSection,
|
|
8
|
+
buildFooter,
|
|
9
|
+
} from './layout.js';
|
|
10
|
+
import { dim } from '../utils/colors.js';
|
|
11
|
+
|
|
12
|
+
/**
|
|
13
|
+
* Render dashboard once (static mode)
|
|
14
|
+
*/
|
|
15
|
+
export function renderDashboard(targetDir) {
|
|
16
|
+
const data = readDashboardData(targetDir);
|
|
17
|
+
|
|
18
|
+
if (!data.session) {
|
|
19
|
+
console.log('No chati.dev session found. Run `npx chati-dev init` first.');
|
|
20
|
+
return;
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
const lines = [
|
|
24
|
+
...buildHeader(data),
|
|
25
|
+
...buildProjectInfo(data),
|
|
26
|
+
'',
|
|
27
|
+
...buildClaritySection(data),
|
|
28
|
+
'',
|
|
29
|
+
...buildBuildSection(data),
|
|
30
|
+
'',
|
|
31
|
+
...buildValidateSection(data),
|
|
32
|
+
'',
|
|
33
|
+
...buildFooter(data),
|
|
34
|
+
];
|
|
35
|
+
|
|
36
|
+
console.log();
|
|
37
|
+
for (const line of lines) {
|
|
38
|
+
console.log(line);
|
|
39
|
+
}
|
|
40
|
+
console.log();
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
/**
|
|
44
|
+
* Render dashboard in watch mode (auto-refresh)
|
|
45
|
+
*/
|
|
46
|
+
export function renderDashboardWatch(targetDir, intervalMs = 5000) {
|
|
47
|
+
// Initial render
|
|
48
|
+
console.clear();
|
|
49
|
+
renderDashboard(targetDir);
|
|
50
|
+
console.log(dim(` Auto-refreshing every ${intervalMs / 1000}s. Press Ctrl+C to exit.`));
|
|
51
|
+
|
|
52
|
+
// Set up interval
|
|
53
|
+
const timer = setInterval(() => {
|
|
54
|
+
console.clear();
|
|
55
|
+
renderDashboard(targetDir);
|
|
56
|
+
console.log(dim(` Auto-refreshing every ${intervalMs / 1000}s. Press Ctrl+C to exit.`));
|
|
57
|
+
console.log(dim(` Last update: ${new Date().toLocaleTimeString()}`));
|
|
58
|
+
}, intervalMs);
|
|
59
|
+
|
|
60
|
+
// Handle exit
|
|
61
|
+
process.on('SIGINT', () => {
|
|
62
|
+
clearInterval(timer);
|
|
63
|
+
console.log();
|
|
64
|
+
process.exit(0);
|
|
65
|
+
});
|
|
66
|
+
|
|
67
|
+
return timer;
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
/**
|
|
71
|
+
* Render plain text fallback (for terminals without TUI support)
|
|
72
|
+
*/
|
|
73
|
+
export function renderPlainDashboard(targetDir) {
|
|
74
|
+
const data = readDashboardData(targetDir);
|
|
75
|
+
|
|
76
|
+
if (!data.session) {
|
|
77
|
+
console.log('No chati.dev session found. Run `npx chati-dev init` first.');
|
|
78
|
+
return;
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
const session = data.session;
|
|
82
|
+
const project = session.project || {};
|
|
83
|
+
|
|
84
|
+
console.log();
|
|
85
|
+
console.log('=== chati.dev Dashboard ===');
|
|
86
|
+
console.log();
|
|
87
|
+
console.log(`Project: ${project.name || 'unknown'}`);
|
|
88
|
+
console.log(`Type: ${project.type || 'unknown'}`);
|
|
89
|
+
console.log(`Phase: ${project.state || 'unknown'}`);
|
|
90
|
+
console.log(`Mode: ${session.execution_mode || 'interactive'}`);
|
|
91
|
+
console.log(`Language: ${session.language || 'en'}`);
|
|
92
|
+
console.log();
|
|
93
|
+
|
|
94
|
+
console.log('--- Agent Scores ---');
|
|
95
|
+
for (const [name, info] of Object.entries(data.agentScores)) {
|
|
96
|
+
const status = info.status === 'pending' ? '--' : info.status === 'in_progress' ? '...' : String(info.score);
|
|
97
|
+
console.log(` ${name.padEnd(20)} ${status}`);
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
console.log();
|
|
101
|
+
console.log(`Blockers: ${data.blockers.length || 'None'}`);
|
|
102
|
+
console.log(`Gotchas: ${data.gotchas.length} patterns`);
|
|
103
|
+
console.log();
|
|
104
|
+
}
|