chati-dev 1.4.0 → 2.0.2
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 +40 -24
- package/framework/agents/build/dev.md +343 -0
- package/framework/agents/clarity/architect.md +112 -0
- package/framework/agents/clarity/brief.md +182 -0
- package/framework/agents/clarity/brownfield-wu.md +181 -0
- package/framework/agents/clarity/detail.md +110 -0
- package/framework/agents/clarity/greenfield-wu.md +153 -0
- package/framework/agents/clarity/ux.md +112 -0
- package/framework/config.yaml +3 -3
- package/framework/constitution.md +31 -1
- package/framework/context/governance.md +37 -0
- package/framework/context/protocols.md +34 -0
- package/framework/context/quality.md +27 -0
- package/framework/context/root.md +24 -0
- package/framework/data/entity-registry.yaml +1 -1
- package/framework/domains/agents/architect.yaml +51 -0
- package/framework/domains/agents/brief.yaml +47 -0
- package/framework/domains/agents/brownfield-wu.yaml +49 -0
- package/framework/domains/agents/detail.yaml +47 -0
- package/framework/domains/agents/dev.yaml +49 -0
- package/framework/domains/agents/devops.yaml +43 -0
- package/framework/domains/agents/greenfield-wu.yaml +47 -0
- package/framework/domains/agents/orchestrator.yaml +49 -0
- package/framework/domains/agents/phases.yaml +47 -0
- package/framework/domains/agents/qa-implementation.yaml +43 -0
- package/framework/domains/agents/qa-planning.yaml +44 -0
- package/framework/domains/agents/tasks.yaml +48 -0
- package/framework/domains/agents/ux.yaml +50 -0
- package/framework/domains/constitution.yaml +77 -0
- package/framework/domains/global.yaml +64 -0
- package/framework/domains/workflows/brownfield-discovery.yaml +16 -0
- package/framework/domains/workflows/brownfield-fullstack.yaml +26 -0
- package/framework/domains/workflows/brownfield-service.yaml +22 -0
- package/framework/domains/workflows/brownfield-ui.yaml +22 -0
- package/framework/domains/workflows/greenfield-fullstack.yaml +26 -0
- package/framework/hooks/constitution-guard.js +101 -0
- package/framework/hooks/mode-governance.js +92 -0
- package/framework/hooks/model-governance.js +76 -0
- package/framework/hooks/prism-engine.js +89 -0
- package/framework/hooks/session-digest.js +60 -0
- package/framework/hooks/settings.json +44 -0
- package/framework/i18n/en.yaml +3 -3
- package/framework/i18n/es.yaml +3 -3
- package/framework/i18n/fr.yaml +3 -3
- package/framework/i18n/pt.yaml +3 -3
- package/framework/intelligence/decision-engine.md +1 -1
- package/framework/migrations/v1.4-to-v2.0.yaml +167 -0
- package/framework/migrations/v2.0-to-v2.0.1.yaml +132 -0
- package/framework/orchestrator/chati.md +284 -6
- package/framework/tasks/architect-api-design.md +63 -0
- package/framework/tasks/architect-consolidate.md +47 -0
- package/framework/tasks/architect-db-design.md +73 -0
- package/framework/tasks/architect-design.md +95 -0
- package/framework/tasks/architect-security-review.md +62 -0
- package/framework/tasks/architect-stack-selection.md +53 -0
- package/framework/tasks/brief-consolidate.md +249 -0
- package/framework/tasks/brief-constraint-identify.md +277 -0
- package/framework/tasks/brief-extract-requirements.md +339 -0
- package/framework/tasks/brief-stakeholder-map.md +176 -0
- package/framework/tasks/brief-validate-completeness.md +121 -0
- package/framework/tasks/brownfield-wu-architecture-map.md +394 -0
- package/framework/tasks/brownfield-wu-deep-discovery.md +312 -0
- package/framework/tasks/brownfield-wu-dependency-scan.md +359 -0
- package/framework/tasks/brownfield-wu-migration-plan.md +483 -0
- package/framework/tasks/brownfield-wu-report.md +325 -0
- package/framework/tasks/brownfield-wu-risk-assess.md +424 -0
- package/framework/tasks/detail-acceptance-criteria.md +372 -0
- package/framework/tasks/detail-consolidate.md +138 -0
- package/framework/tasks/detail-edge-case-analysis.md +300 -0
- package/framework/tasks/detail-expand-prd.md +389 -0
- package/framework/tasks/detail-nfr-extraction.md +223 -0
- package/framework/tasks/dev-code-review.md +404 -0
- package/framework/tasks/dev-consolidate.md +543 -0
- package/framework/tasks/dev-debug.md +322 -0
- package/framework/tasks/dev-implement.md +252 -0
- package/framework/tasks/dev-iterate.md +411 -0
- package/framework/tasks/dev-pr-prepare.md +497 -0
- package/framework/tasks/dev-refactor.md +342 -0
- package/framework/tasks/dev-test-write.md +306 -0
- package/framework/tasks/devops-ci-setup.md +412 -0
- package/framework/tasks/devops-consolidate.md +712 -0
- package/framework/tasks/devops-deploy-config.md +598 -0
- package/framework/tasks/devops-monitoring-setup.md +658 -0
- package/framework/tasks/devops-release-prepare.md +673 -0
- package/framework/tasks/greenfield-wu-analyze-empty.md +169 -0
- package/framework/tasks/greenfield-wu-report.md +266 -0
- package/framework/tasks/greenfield-wu-scaffold-detection.md +203 -0
- package/framework/tasks/greenfield-wu-tech-stack-assess.md +255 -0
- package/framework/tasks/orchestrator-deviation.md +260 -0
- package/framework/tasks/orchestrator-escalate.md +276 -0
- package/framework/tasks/orchestrator-handoff.md +243 -0
- package/framework/tasks/orchestrator-health.md +372 -0
- package/framework/tasks/orchestrator-mode-switch.md +262 -0
- package/framework/tasks/orchestrator-resume.md +189 -0
- package/framework/tasks/orchestrator-route.md +169 -0
- package/framework/tasks/orchestrator-spawn-terminal.md +358 -0
- package/framework/tasks/orchestrator-status.md +260 -0
- package/framework/tasks/orchestrator-suggest-mode.md +372 -0
- package/framework/tasks/phases-breakdown.md +91 -0
- package/framework/tasks/phases-dependency-mapping.md +67 -0
- package/framework/tasks/phases-mvp-scoping.md +94 -0
- package/framework/tasks/qa-impl-consolidate.md +522 -0
- package/framework/tasks/qa-impl-performance-test.md +487 -0
- package/framework/tasks/qa-impl-regression-check.md +413 -0
- package/framework/tasks/qa-impl-sast-scan.md +402 -0
- package/framework/tasks/qa-impl-test-execute.md +344 -0
- package/framework/tasks/qa-impl-verdict.md +339 -0
- package/framework/tasks/qa-planning-consolidate.md +309 -0
- package/framework/tasks/qa-planning-coverage-plan.md +338 -0
- package/framework/tasks/qa-planning-gate-define.md +339 -0
- package/framework/tasks/qa-planning-risk-matrix.md +631 -0
- package/framework/tasks/qa-planning-test-strategy.md +217 -0
- package/framework/tasks/tasks-acceptance-write.md +75 -0
- package/framework/tasks/tasks-consolidate.md +57 -0
- package/framework/tasks/tasks-decompose.md +80 -0
- package/framework/tasks/tasks-estimate.md +66 -0
- package/framework/tasks/ux-a11y-check.md +49 -0
- package/framework/tasks/ux-component-map.md +55 -0
- package/framework/tasks/ux-consolidate.md +46 -0
- package/framework/tasks/ux-user-flow.md +46 -0
- package/framework/tasks/ux-wireframe.md +76 -0
- package/package.json +2 -2
- package/scripts/bundle-framework.js +2 -0
- package/scripts/changelog-generator.js +222 -0
- package/scripts/codebase-mapper.js +728 -0
- package/scripts/commit-message-generator.js +167 -0
- package/scripts/coverage-analyzer.js +260 -0
- package/scripts/dependency-analyzer.js +280 -0
- package/scripts/framework-analyzer.js +308 -0
- package/scripts/generate-constitution-domain.js +253 -0
- package/scripts/health-check.js +481 -0
- package/scripts/ide-sync.js +327 -0
- package/scripts/performance-analyzer.js +325 -0
- package/scripts/plan-tracker.js +278 -0
- package/scripts/populate-entity-registry.js +481 -0
- package/scripts/pr-review.js +317 -0
- package/scripts/rollback-manager.js +310 -0
- package/scripts/stuck-detector.js +343 -0
- package/scripts/test-quality-assessment.js +257 -0
- package/scripts/validate-agents.js +367 -0
- package/scripts/validate-tasks.js +465 -0
- package/src/autonomy/autonomous-gate.js +293 -0
- package/src/autonomy/index.js +51 -0
- package/src/autonomy/mode-manager.js +225 -0
- package/src/autonomy/mode-suggester.js +283 -0
- package/src/autonomy/progress-reporter.js +268 -0
- package/src/autonomy/safety-net.js +320 -0
- package/src/context/bracket-tracker.js +79 -0
- package/src/context/domain-loader.js +107 -0
- package/src/context/engine.js +144 -0
- package/src/context/formatter.js +184 -0
- package/src/context/index.js +4 -0
- package/src/context/layers/l0-constitution.js +28 -0
- package/src/context/layers/l1-global.js +37 -0
- package/src/context/layers/l2-agent.js +39 -0
- package/src/context/layers/l3-workflow.js +42 -0
- package/src/context/layers/l4-task.js +24 -0
- package/src/decision/analyzer.js +167 -0
- package/src/decision/engine.js +270 -0
- package/src/decision/index.js +38 -0
- package/src/decision/registry-healer.js +450 -0
- package/src/decision/registry-updater.js +330 -0
- package/src/gates/circuit-breaker.js +119 -0
- package/src/gates/g1-planning-complete.js +153 -0
- package/src/gates/g2-qa-planning.js +153 -0
- package/src/gates/g3-implementation.js +188 -0
- package/src/gates/g4-qa-implementation.js +207 -0
- package/src/gates/g5-deploy-ready.js +180 -0
- package/src/gates/gate-base.js +144 -0
- package/src/gates/index.js +46 -0
- package/src/installer/brownfield-upgrader.js +249 -0
- package/src/installer/core.js +82 -11
- package/src/installer/file-hasher.js +51 -0
- package/src/installer/manifest.js +117 -0
- package/src/installer/templates.js +17 -15
- package/src/installer/transaction.js +229 -0
- package/src/installer/validator.js +18 -1
- package/src/intelligence/registry-manager.js +2 -2
- package/src/memory/agent-memory.js +255 -0
- package/src/memory/gotchas-injector.js +72 -0
- package/src/memory/gotchas.js +361 -0
- package/src/memory/index.js +35 -0
- package/src/memory/search.js +233 -0
- package/src/memory/session-digest.js +239 -0
- package/src/merger/env-merger.js +112 -0
- package/src/merger/index.js +56 -0
- package/src/merger/replace-merger.js +51 -0
- package/src/merger/yaml-merger.js +127 -0
- package/src/orchestrator/agent-selector.js +285 -0
- package/src/orchestrator/deviation-handler.js +350 -0
- package/src/orchestrator/handoff-engine.js +271 -0
- package/src/orchestrator/index.js +67 -0
- package/src/orchestrator/intent-classifier.js +264 -0
- package/src/orchestrator/pipeline-manager.js +492 -0
- package/src/orchestrator/pipeline-state.js +223 -0
- package/src/orchestrator/session-manager.js +409 -0
- package/src/tasks/executor.js +195 -0
- package/src/tasks/handoff.js +226 -0
- package/src/tasks/index.js +4 -0
- package/src/tasks/loader.js +210 -0
- package/src/tasks/router.js +182 -0
- package/src/terminal/collector.js +216 -0
- package/src/terminal/index.js +30 -0
- package/src/terminal/isolation.js +129 -0
- package/src/terminal/monitor.js +277 -0
- package/src/terminal/spawner.js +269 -0
- package/src/upgrade/checker.js +1 -1
- package/src/wizard/i18n.js +3 -3
|
@@ -0,0 +1,327 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* IDE Sync — Synchronizes agent files to IDE-specific locations.
|
|
5
|
+
*
|
|
6
|
+
* Exports:
|
|
7
|
+
* IDE_CONFIGS — Map of IDE names to their config.
|
|
8
|
+
* syncToIDE(targetDir, frameworkDir, ideName) → { synced: number, files: [] }
|
|
9
|
+
* syncAllIDEs(targetDir, frameworkDir, installedIDEs) → { results: { ide: syncResult } }
|
|
10
|
+
* detectInstalledIDEs(targetDir) → string[]
|
|
11
|
+
* buildIDEContent(agentContent, format, agentName) → transformed content
|
|
12
|
+
*/
|
|
13
|
+
|
|
14
|
+
import { readFileSync, writeFileSync, existsSync, readdirSync, mkdirSync } from 'fs';
|
|
15
|
+
import { join, basename, relative } from 'path';
|
|
16
|
+
|
|
17
|
+
/**
|
|
18
|
+
* IDE configuration definitions.
|
|
19
|
+
*/
|
|
20
|
+
export const IDE_CONFIGS = {
|
|
21
|
+
'claude-code': {
|
|
22
|
+
commandDir: '.claude/commands/',
|
|
23
|
+
extension: '.md',
|
|
24
|
+
format: 'markdown',
|
|
25
|
+
description: 'Claude Code (Anthropic)',
|
|
26
|
+
},
|
|
27
|
+
cursor: {
|
|
28
|
+
rulesDir: '.cursor/rules/',
|
|
29
|
+
extension: '.mdc',
|
|
30
|
+
format: 'cursor-rules',
|
|
31
|
+
description: 'Cursor',
|
|
32
|
+
},
|
|
33
|
+
windsurf: {
|
|
34
|
+
rulesDir: '.windsurf/rules/',
|
|
35
|
+
extension: '.md',
|
|
36
|
+
format: 'markdown',
|
|
37
|
+
description: 'Windsurf',
|
|
38
|
+
},
|
|
39
|
+
vscode: {
|
|
40
|
+
configDir: '.vscode/',
|
|
41
|
+
extension: '.md',
|
|
42
|
+
format: 'markdown',
|
|
43
|
+
description: 'Visual Studio Code',
|
|
44
|
+
},
|
|
45
|
+
antigravity: {
|
|
46
|
+
agentsDir: '.antigravity/agents/',
|
|
47
|
+
extension: '.md',
|
|
48
|
+
format: 'markdown',
|
|
49
|
+
description: 'AntiGravity',
|
|
50
|
+
},
|
|
51
|
+
'gemini-cli': {
|
|
52
|
+
configDir: '.gemini/',
|
|
53
|
+
extension: '.md',
|
|
54
|
+
format: 'markdown',
|
|
55
|
+
description: 'Gemini CLI',
|
|
56
|
+
},
|
|
57
|
+
'github-copilot': {
|
|
58
|
+
instructionsDir: '.github/copilot/',
|
|
59
|
+
extension: '.md',
|
|
60
|
+
format: 'markdown',
|
|
61
|
+
description: 'GitHub Copilot',
|
|
62
|
+
},
|
|
63
|
+
};
|
|
64
|
+
|
|
65
|
+
// ---------------------------------------------------------------------------
|
|
66
|
+
// Internal Helpers
|
|
67
|
+
// ---------------------------------------------------------------------------
|
|
68
|
+
|
|
69
|
+
/**
|
|
70
|
+
* Collect all agent .md files from the framework directory.
|
|
71
|
+
* @param {string} frameworkDir - Path to the framework directory.
|
|
72
|
+
* @returns {object[]} Array of { name, path, content }.
|
|
73
|
+
*/
|
|
74
|
+
function collectAgentFiles(frameworkDir) {
|
|
75
|
+
const agents = [];
|
|
76
|
+
|
|
77
|
+
// Collect agents from subdirectories
|
|
78
|
+
const agentsDir = join(frameworkDir, 'agents');
|
|
79
|
+
if (existsSync(agentsDir)) {
|
|
80
|
+
const categories = readdirSync(agentsDir, { withFileTypes: true })
|
|
81
|
+
.filter(e => e.isDirectory())
|
|
82
|
+
.map(e => e.name);
|
|
83
|
+
|
|
84
|
+
for (const category of categories) {
|
|
85
|
+
const catDir = join(agentsDir, category);
|
|
86
|
+
try {
|
|
87
|
+
const files = readdirSync(catDir).filter(f => f.endsWith('.md'));
|
|
88
|
+
for (const file of files) {
|
|
89
|
+
const fullPath = join(catDir, file);
|
|
90
|
+
agents.push({
|
|
91
|
+
name: basename(file, '.md'),
|
|
92
|
+
path: fullPath,
|
|
93
|
+
content: readFileSync(fullPath, 'utf8'),
|
|
94
|
+
category,
|
|
95
|
+
});
|
|
96
|
+
}
|
|
97
|
+
} catch {
|
|
98
|
+
// Skip unreadable directories
|
|
99
|
+
}
|
|
100
|
+
}
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
// Collect orchestrator
|
|
104
|
+
const orchPath = join(frameworkDir, 'orchestrator', 'chati.md');
|
|
105
|
+
if (existsSync(orchPath)) {
|
|
106
|
+
agents.push({
|
|
107
|
+
name: 'chati',
|
|
108
|
+
path: orchPath,
|
|
109
|
+
content: readFileSync(orchPath, 'utf8'),
|
|
110
|
+
category: 'orchestrator',
|
|
111
|
+
});
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
return agents;
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
/**
|
|
118
|
+
* Get the target directory path for a specific IDE.
|
|
119
|
+
* @param {string} targetDir - Project root directory.
|
|
120
|
+
* @param {string} ideName - IDE identifier.
|
|
121
|
+
* @returns {string} Target directory for sync.
|
|
122
|
+
*/
|
|
123
|
+
function getIDETargetDir(targetDir, ideName) {
|
|
124
|
+
const config = IDE_CONFIGS[ideName];
|
|
125
|
+
if (!config) return null;
|
|
126
|
+
|
|
127
|
+
// Pick the first defined directory key
|
|
128
|
+
const dirKey = config.commandDir || config.rulesDir || config.configDir
|
|
129
|
+
|| config.agentsDir || config.instructionsDir;
|
|
130
|
+
|
|
131
|
+
return join(targetDir, dirKey);
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
/**
|
|
135
|
+
* Transform agent markdown content for a specific IDE format.
|
|
136
|
+
* @param {string} agentContent - Raw agent markdown content.
|
|
137
|
+
* @param {string} format - Target format ('markdown' | 'cursor-rules').
|
|
138
|
+
* @param {string} agentName - Agent name for metadata.
|
|
139
|
+
* @returns {string} Transformed content.
|
|
140
|
+
*/
|
|
141
|
+
export function buildIDEContent(agentContent, format, agentName = '') {
|
|
142
|
+
switch (format) {
|
|
143
|
+
case 'cursor-rules': {
|
|
144
|
+
// Cursor uses .mdc format with YAML-like frontmatter
|
|
145
|
+
const lines = [
|
|
146
|
+
'---',
|
|
147
|
+
`description: chati.dev agent — ${agentName}`,
|
|
148
|
+
'globs:',
|
|
149
|
+
'alwaysApply: false',
|
|
150
|
+
'---',
|
|
151
|
+
'',
|
|
152
|
+
agentContent,
|
|
153
|
+
];
|
|
154
|
+
return lines.join('\n');
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
case 'markdown':
|
|
158
|
+
default:
|
|
159
|
+
return agentContent;
|
|
160
|
+
}
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
// ---------------------------------------------------------------------------
|
|
164
|
+
// Public API
|
|
165
|
+
// ---------------------------------------------------------------------------
|
|
166
|
+
|
|
167
|
+
/**
|
|
168
|
+
* Detect which IDEs have existing config directories in the project.
|
|
169
|
+
* @param {string} targetDir - Project root directory.
|
|
170
|
+
* @returns {string[]} Array of detected IDE names.
|
|
171
|
+
*/
|
|
172
|
+
export function detectInstalledIDEs(targetDir) {
|
|
173
|
+
const detected = [];
|
|
174
|
+
|
|
175
|
+
for (const [ideName, config] of Object.entries(IDE_CONFIGS)) {
|
|
176
|
+
const dirKey = config.commandDir || config.rulesDir || config.configDir
|
|
177
|
+
|| config.agentsDir || config.instructionsDir;
|
|
178
|
+
const checkDir = dirKey.split('/')[0]; // Check first directory component
|
|
179
|
+
|
|
180
|
+
if (existsSync(join(targetDir, checkDir))) {
|
|
181
|
+
detected.push(ideName);
|
|
182
|
+
}
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
return detected;
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
/**
|
|
189
|
+
* Sync agent files to a specific IDE's location.
|
|
190
|
+
* @param {string} targetDir - Project root directory.
|
|
191
|
+
* @param {string} frameworkDir - Path to the framework directory.
|
|
192
|
+
* @param {string} ideName - IDE identifier.
|
|
193
|
+
* @returns {{ synced: number, files: string[], errors: string[] }}
|
|
194
|
+
*/
|
|
195
|
+
export function syncToIDE(targetDir, frameworkDir, ideName) {
|
|
196
|
+
const config = IDE_CONFIGS[ideName];
|
|
197
|
+
if (!config) {
|
|
198
|
+
return { synced: 0, files: [], errors: [`Unknown IDE: ${ideName}`] };
|
|
199
|
+
}
|
|
200
|
+
|
|
201
|
+
const result = { synced: 0, files: [], errors: [] };
|
|
202
|
+
|
|
203
|
+
const ideTargetDir = getIDETargetDir(targetDir, ideName);
|
|
204
|
+
if (!ideTargetDir) {
|
|
205
|
+
result.errors.push(`Cannot determine target directory for ${ideName}`);
|
|
206
|
+
return result;
|
|
207
|
+
}
|
|
208
|
+
|
|
209
|
+
// Collect agent files
|
|
210
|
+
const agents = collectAgentFiles(frameworkDir);
|
|
211
|
+
if (agents.length === 0) {
|
|
212
|
+
result.errors.push('No agent files found in framework directory');
|
|
213
|
+
return result;
|
|
214
|
+
}
|
|
215
|
+
|
|
216
|
+
// Create target directory
|
|
217
|
+
mkdirSync(ideTargetDir, { recursive: true });
|
|
218
|
+
|
|
219
|
+
// Sync each agent
|
|
220
|
+
for (const agent of agents) {
|
|
221
|
+
const targetFileName = `${agent.name}${config.extension}`;
|
|
222
|
+
const targetPath = join(ideTargetDir, targetFileName);
|
|
223
|
+
|
|
224
|
+
try {
|
|
225
|
+
const content = buildIDEContent(agent.content, config.format, agent.name);
|
|
226
|
+
writeFileSync(targetPath, content, 'utf8');
|
|
227
|
+
result.synced++;
|
|
228
|
+
result.files.push(relative(targetDir, targetPath));
|
|
229
|
+
} catch (err) {
|
|
230
|
+
result.errors.push(`Failed to write ${targetFileName}: ${err.message}`);
|
|
231
|
+
}
|
|
232
|
+
}
|
|
233
|
+
|
|
234
|
+
return result;
|
|
235
|
+
}
|
|
236
|
+
|
|
237
|
+
/**
|
|
238
|
+
* Sync agent files to all specified IDEs.
|
|
239
|
+
* @param {string} targetDir - Project root directory.
|
|
240
|
+
* @param {string} frameworkDir - Path to the framework directory.
|
|
241
|
+
* @param {string[]} installedIDEs - List of IDE names to sync to.
|
|
242
|
+
* @returns {{ results: object, totalSynced: number, totalErrors: number }}
|
|
243
|
+
*/
|
|
244
|
+
export function syncAllIDEs(targetDir, frameworkDir, installedIDEs) {
|
|
245
|
+
const results = {};
|
|
246
|
+
let totalSynced = 0;
|
|
247
|
+
let totalErrors = 0;
|
|
248
|
+
|
|
249
|
+
for (const ide of installedIDEs) {
|
|
250
|
+
const result = syncToIDE(targetDir, frameworkDir, ide);
|
|
251
|
+
results[ide] = result;
|
|
252
|
+
totalSynced += result.synced;
|
|
253
|
+
totalErrors += result.errors.length;
|
|
254
|
+
}
|
|
255
|
+
|
|
256
|
+
return { results, totalSynced, totalErrors };
|
|
257
|
+
}
|
|
258
|
+
|
|
259
|
+
/**
|
|
260
|
+
* Format sync results as a human-readable string.
|
|
261
|
+
* @param {object} syncResults - Results from syncAllIDEs.
|
|
262
|
+
* @returns {string}
|
|
263
|
+
*/
|
|
264
|
+
export function formatSyncReport(syncResults) {
|
|
265
|
+
const lines = [
|
|
266
|
+
'=== IDE Sync Report ===',
|
|
267
|
+
`Total synced: ${syncResults.totalSynced} files`,
|
|
268
|
+
`Total errors: ${syncResults.totalErrors}`,
|
|
269
|
+
'',
|
|
270
|
+
];
|
|
271
|
+
|
|
272
|
+
for (const [ide, result] of Object.entries(syncResults.results)) {
|
|
273
|
+
const config = IDE_CONFIGS[ide];
|
|
274
|
+
const desc = config?.description || ide;
|
|
275
|
+
const symbol = result.errors.length === 0 ? '[OK]' : '[WARN]';
|
|
276
|
+
|
|
277
|
+
lines.push(` ${symbol} ${desc}: ${result.synced} files synced`);
|
|
278
|
+
for (const file of result.files) {
|
|
279
|
+
lines.push(` ${file}`);
|
|
280
|
+
}
|
|
281
|
+
for (const err of result.errors) {
|
|
282
|
+
lines.push(` ERROR: ${err}`);
|
|
283
|
+
}
|
|
284
|
+
}
|
|
285
|
+
|
|
286
|
+
lines.push('');
|
|
287
|
+
lines.push('=== End Report ===');
|
|
288
|
+
return lines.join('\n');
|
|
289
|
+
}
|
|
290
|
+
|
|
291
|
+
// ---------------------------------------------------------------------------
|
|
292
|
+
// CLI entrypoint
|
|
293
|
+
// ---------------------------------------------------------------------------
|
|
294
|
+
|
|
295
|
+
const isMainModule = process.argv[1] && (
|
|
296
|
+
process.argv[1].endsWith('ide-sync.js') ||
|
|
297
|
+
process.argv[1].endsWith('ide-sync')
|
|
298
|
+
);
|
|
299
|
+
|
|
300
|
+
if (isMainModule) {
|
|
301
|
+
const targetDir = process.argv[2] || process.cwd();
|
|
302
|
+
const frameworkDir = process.argv[3] || join(targetDir, 'chati.dev');
|
|
303
|
+
const specificIDE = process.argv[4];
|
|
304
|
+
|
|
305
|
+
if (specificIDE) {
|
|
306
|
+
console.log(`Syncing to ${specificIDE}...`);
|
|
307
|
+
const result = syncToIDE(targetDir, frameworkDir, specificIDE);
|
|
308
|
+
console.log(`Synced: ${result.synced} files`);
|
|
309
|
+
for (const file of result.files) console.log(` ${file}`);
|
|
310
|
+
if (result.errors.length > 0) {
|
|
311
|
+
for (const err of result.errors) console.log(` ERROR: ${err}`);
|
|
312
|
+
}
|
|
313
|
+
} else {
|
|
314
|
+
console.log('Detecting installed IDEs...');
|
|
315
|
+
const detected = detectInstalledIDEs(targetDir);
|
|
316
|
+
|
|
317
|
+
if (detected.length === 0) {
|
|
318
|
+
console.log('No IDE configurations detected. Specify IDE: node scripts/ide-sync.js <targetDir> <frameworkDir> <ideName>');
|
|
319
|
+
console.log(`Available: ${Object.keys(IDE_CONFIGS).join(', ')}`);
|
|
320
|
+
process.exit(0);
|
|
321
|
+
}
|
|
322
|
+
|
|
323
|
+
console.log(`Detected: ${detected.join(', ')}`);
|
|
324
|
+
const results = syncAllIDEs(targetDir, frameworkDir, detected);
|
|
325
|
+
console.log(formatSyncReport(results));
|
|
326
|
+
}
|
|
327
|
+
}
|
|
@@ -0,0 +1,325 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Performance Analyzer — Static analysis of code patterns that affect performance.
|
|
3
|
+
*
|
|
4
|
+
* Scans source files for large files, deep nesting, long functions,
|
|
5
|
+
* excessive imports, and synchronous filesystem operations.
|
|
6
|
+
*
|
|
7
|
+
* @module scripts/performance-analyzer
|
|
8
|
+
*/
|
|
9
|
+
|
|
10
|
+
import { readFileSync, readdirSync, statSync } from 'node:fs';
|
|
11
|
+
import { join, extname } from 'node:path';
|
|
12
|
+
|
|
13
|
+
/**
|
|
14
|
+
* @typedef {Object} ComplexityResult
|
|
15
|
+
* @property {number} cyclomaticComplexity
|
|
16
|
+
* @property {number} nestingDepth
|
|
17
|
+
* @property {Array<{ name: string, lines: number }>} functionLengths
|
|
18
|
+
*/
|
|
19
|
+
|
|
20
|
+
/**
|
|
21
|
+
* @typedef {Object} FilePerformanceReport
|
|
22
|
+
* @property {string} filePath
|
|
23
|
+
* @property {number} lineCount
|
|
24
|
+
* @property {number} importCount
|
|
25
|
+
* @property {ComplexityResult} complexity
|
|
26
|
+
* @property {boolean} hasSyncFsOps
|
|
27
|
+
* @property {string[]} warnings
|
|
28
|
+
* @property {string[]} errors
|
|
29
|
+
*/
|
|
30
|
+
|
|
31
|
+
/**
|
|
32
|
+
* @typedef {Object} PerformanceReport
|
|
33
|
+
* @property {FilePerformanceReport[]} files
|
|
34
|
+
* @property {{ totalFiles: number, totalWarnings: number, totalErrors: number }} summary
|
|
35
|
+
*/
|
|
36
|
+
|
|
37
|
+
// Thresholds
|
|
38
|
+
const THRESHOLDS = {
|
|
39
|
+
fileSizeWarning: 500,
|
|
40
|
+
fileSizeError: 1000,
|
|
41
|
+
nestingDepth: 4,
|
|
42
|
+
functionLength: 50,
|
|
43
|
+
importCount: 15,
|
|
44
|
+
cyclomaticComplexityWarning: 10,
|
|
45
|
+
cyclomaticComplexityError: 20,
|
|
46
|
+
};
|
|
47
|
+
|
|
48
|
+
// Sync fs methods that should be avoided in non-script files
|
|
49
|
+
const SYNC_FS_PATTERN = /\b(?:readFileSync|writeFileSync|appendFileSync|mkdirSync|rmdirSync|unlinkSync|renameSync|copyFileSync|readdirSync|statSync|lstatSync|existsSync|accessSync)\b/g;
|
|
50
|
+
|
|
51
|
+
/**
|
|
52
|
+
* Collects JS/TS source files recursively (excludes tests, node_modules, .git).
|
|
53
|
+
* @param {string} dir
|
|
54
|
+
* @returns {string[]}
|
|
55
|
+
*/
|
|
56
|
+
function collectSourceFiles(dir) {
|
|
57
|
+
const results = [];
|
|
58
|
+
const sourceExtensions = new Set(['.js', '.ts', '.mjs', '.mts']);
|
|
59
|
+
|
|
60
|
+
function walk(currentDir) {
|
|
61
|
+
let entries;
|
|
62
|
+
try {
|
|
63
|
+
entries = readdirSync(currentDir);
|
|
64
|
+
} catch {
|
|
65
|
+
return;
|
|
66
|
+
}
|
|
67
|
+
for (const entry of entries) {
|
|
68
|
+
if (entry === 'node_modules' || entry === '.git' || entry === 'dist' || entry === 'build') continue;
|
|
69
|
+
const fullPath = join(currentDir, entry);
|
|
70
|
+
let stat;
|
|
71
|
+
try {
|
|
72
|
+
stat = statSync(fullPath);
|
|
73
|
+
} catch {
|
|
74
|
+
continue;
|
|
75
|
+
}
|
|
76
|
+
if (stat.isDirectory()) {
|
|
77
|
+
walk(fullPath);
|
|
78
|
+
} else if (stat.isFile() && sourceExtensions.has(extname(entry))) {
|
|
79
|
+
// Skip test files
|
|
80
|
+
if (entry.endsWith('.test.js') || entry.endsWith('.spec.js') ||
|
|
81
|
+
entry.endsWith('.test.ts') || entry.endsWith('.spec.ts')) {
|
|
82
|
+
continue;
|
|
83
|
+
}
|
|
84
|
+
results.push(fullPath);
|
|
85
|
+
}
|
|
86
|
+
}
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
walk(dir);
|
|
90
|
+
return results.sort();
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
/**
|
|
94
|
+
* Calculates code complexity metrics for a given source string.
|
|
95
|
+
*
|
|
96
|
+
* @param {string} code
|
|
97
|
+
* @returns {ComplexityResult}
|
|
98
|
+
*/
|
|
99
|
+
export function calculateComplexity(code) {
|
|
100
|
+
const lines = code.split('\n');
|
|
101
|
+
|
|
102
|
+
// Cyclomatic complexity: count decision points
|
|
103
|
+
const decisionPatterns = [
|
|
104
|
+
/\bif\s*\(/g,
|
|
105
|
+
/\belse\s+if\s*\(/g,
|
|
106
|
+
/\bfor\s*\(/g,
|
|
107
|
+
/\bwhile\s*\(/g,
|
|
108
|
+
/\bswitch\s*\(/g,
|
|
109
|
+
/\bcase\s+/g,
|
|
110
|
+
/\bcatch\s*\(/g,
|
|
111
|
+
/\?\s*[^:]/g, // ternary
|
|
112
|
+
/&&/g,
|
|
113
|
+
/\|\|/g,
|
|
114
|
+
];
|
|
115
|
+
|
|
116
|
+
let cyclomaticComplexity = 1; // base complexity
|
|
117
|
+
for (const pattern of decisionPatterns) {
|
|
118
|
+
const matches = code.match(pattern);
|
|
119
|
+
if (matches) cyclomaticComplexity += matches.length;
|
|
120
|
+
pattern.lastIndex = 0;
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
// Max nesting depth
|
|
124
|
+
let maxNesting = 0;
|
|
125
|
+
let currentNesting = 0;
|
|
126
|
+
for (const line of lines) {
|
|
127
|
+
const trimmed = line.trim();
|
|
128
|
+
// Skip comments and strings (simple heuristic)
|
|
129
|
+
if (trimmed.startsWith('//') || trimmed.startsWith('*') || trimmed.startsWith('/*')) continue;
|
|
130
|
+
|
|
131
|
+
const opens = (trimmed.match(/\{/g) || []).length;
|
|
132
|
+
const closes = (trimmed.match(/\}/g) || []).length;
|
|
133
|
+
currentNesting += opens - closes;
|
|
134
|
+
if (currentNesting > maxNesting) maxNesting = currentNesting;
|
|
135
|
+
if (currentNesting < 0) currentNesting = 0;
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
// Function lengths
|
|
139
|
+
const functionLengths = [];
|
|
140
|
+
const funcStartRegex = /(?:(?:export\s+)?(?:async\s+)?function\s+(\w+)|(?:const|let|var)\s+(\w+)\s*=\s*(?:async\s+)?(?:\([^)]*\)|[^=])\s*=>|(\w+)\s*\([^)]*\)\s*\{)/;
|
|
141
|
+
|
|
142
|
+
let funcName = null;
|
|
143
|
+
let funcStartLine = 0;
|
|
144
|
+
let braceDepth = 0;
|
|
145
|
+
let inFunction = false;
|
|
146
|
+
|
|
147
|
+
for (let i = 0; i < lines.length; i++) {
|
|
148
|
+
const line = lines[i];
|
|
149
|
+
const trimmed = line.trim();
|
|
150
|
+
|
|
151
|
+
if (!inFunction) {
|
|
152
|
+
const match = trimmed.match(funcStartRegex);
|
|
153
|
+
if (match) {
|
|
154
|
+
funcName = match[1] || match[2] || match[3] || 'anonymous';
|
|
155
|
+
funcStartLine = i;
|
|
156
|
+
inFunction = true;
|
|
157
|
+
braceDepth = 0;
|
|
158
|
+
}
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
if (inFunction) {
|
|
162
|
+
const opens = (trimmed.match(/\{/g) || []).length;
|
|
163
|
+
const closes = (trimmed.match(/\}/g) || []).length;
|
|
164
|
+
braceDepth += opens - closes;
|
|
165
|
+
|
|
166
|
+
if (braceDepth <= 0 && i > funcStartLine) {
|
|
167
|
+
const length = i - funcStartLine + 1;
|
|
168
|
+
functionLengths.push({ name: funcName, lines: length });
|
|
169
|
+
inFunction = false;
|
|
170
|
+
funcName = null;
|
|
171
|
+
}
|
|
172
|
+
}
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
return {
|
|
176
|
+
cyclomaticComplexity,
|
|
177
|
+
nestingDepth: maxNesting,
|
|
178
|
+
functionLengths,
|
|
179
|
+
};
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
/**
|
|
183
|
+
* Analyzes a single file for performance-related patterns.
|
|
184
|
+
*
|
|
185
|
+
* @param {string} filePath
|
|
186
|
+
* @returns {FilePerformanceReport}
|
|
187
|
+
*/
|
|
188
|
+
export function analyzeFile(filePath) {
|
|
189
|
+
let content;
|
|
190
|
+
try {
|
|
191
|
+
content = readFileSync(filePath, 'utf-8');
|
|
192
|
+
} catch (err) {
|
|
193
|
+
return {
|
|
194
|
+
filePath,
|
|
195
|
+
lineCount: 0,
|
|
196
|
+
importCount: 0,
|
|
197
|
+
complexity: { cyclomaticComplexity: 0, nestingDepth: 0, functionLengths: [] },
|
|
198
|
+
hasSyncFsOps: false,
|
|
199
|
+
warnings: [`Cannot read file: ${err.message}`],
|
|
200
|
+
errors: [],
|
|
201
|
+
};
|
|
202
|
+
}
|
|
203
|
+
|
|
204
|
+
const lines = content.split('\n');
|
|
205
|
+
const lineCount = lines.length;
|
|
206
|
+
const warnings = [];
|
|
207
|
+
const errors = [];
|
|
208
|
+
|
|
209
|
+
// Count imports
|
|
210
|
+
const importMatches = content.match(/\b(?:import\s+|require\s*\()/g) || [];
|
|
211
|
+
const importCount = importMatches.length;
|
|
212
|
+
|
|
213
|
+
// Check sync fs operations (only flag in non-script files)
|
|
214
|
+
const isScript = filePath.includes('/scripts/') || filePath.includes('/bin/');
|
|
215
|
+
const syncMatches = content.match(SYNC_FS_PATTERN) || [];
|
|
216
|
+
const hasSyncFsOps = syncMatches.length > 0;
|
|
217
|
+
|
|
218
|
+
// Calculate complexity
|
|
219
|
+
const complexity = calculateComplexity(content);
|
|
220
|
+
|
|
221
|
+
// Generate warnings and errors
|
|
222
|
+
if (lineCount > THRESHOLDS.fileSizeError) {
|
|
223
|
+
errors.push(`File has ${lineCount} lines (threshold: ${THRESHOLDS.fileSizeError})`);
|
|
224
|
+
} else if (lineCount > THRESHOLDS.fileSizeWarning) {
|
|
225
|
+
warnings.push(`File has ${lineCount} lines (threshold: ${THRESHOLDS.fileSizeWarning})`);
|
|
226
|
+
}
|
|
227
|
+
|
|
228
|
+
if (complexity.nestingDepth > THRESHOLDS.nestingDepth) {
|
|
229
|
+
warnings.push(`Max nesting depth: ${complexity.nestingDepth} (threshold: ${THRESHOLDS.nestingDepth})`);
|
|
230
|
+
}
|
|
231
|
+
|
|
232
|
+
if (importCount > THRESHOLDS.importCount) {
|
|
233
|
+
warnings.push(`${importCount} imports (threshold: ${THRESHOLDS.importCount})`);
|
|
234
|
+
}
|
|
235
|
+
|
|
236
|
+
if (hasSyncFsOps && !isScript) {
|
|
237
|
+
warnings.push(`${syncMatches.length} synchronous filesystem operation(s) in non-script file`);
|
|
238
|
+
}
|
|
239
|
+
|
|
240
|
+
for (const func of complexity.functionLengths) {
|
|
241
|
+
if (func.lines > THRESHOLDS.functionLength) {
|
|
242
|
+
warnings.push(`Function "${func.name}" is ${func.lines} lines (threshold: ${THRESHOLDS.functionLength})`);
|
|
243
|
+
}
|
|
244
|
+
}
|
|
245
|
+
|
|
246
|
+
if (complexity.cyclomaticComplexity > THRESHOLDS.cyclomaticComplexityError) {
|
|
247
|
+
errors.push(`Cyclomatic complexity: ${complexity.cyclomaticComplexity} (threshold: ${THRESHOLDS.cyclomaticComplexityError})`);
|
|
248
|
+
} else if (complexity.cyclomaticComplexity > THRESHOLDS.cyclomaticComplexityWarning) {
|
|
249
|
+
warnings.push(`Cyclomatic complexity: ${complexity.cyclomaticComplexity} (threshold: ${THRESHOLDS.cyclomaticComplexityWarning})`);
|
|
250
|
+
}
|
|
251
|
+
|
|
252
|
+
return {
|
|
253
|
+
filePath,
|
|
254
|
+
lineCount,
|
|
255
|
+
importCount,
|
|
256
|
+
complexity,
|
|
257
|
+
hasSyncFsOps,
|
|
258
|
+
warnings,
|
|
259
|
+
errors,
|
|
260
|
+
};
|
|
261
|
+
}
|
|
262
|
+
|
|
263
|
+
/**
|
|
264
|
+
* Analyzes all source files in a directory.
|
|
265
|
+
*
|
|
266
|
+
* @param {string} srcDir
|
|
267
|
+
* @returns {PerformanceReport}
|
|
268
|
+
*/
|
|
269
|
+
export function analyzePerformance(srcDir) {
|
|
270
|
+
const sourceFiles = collectSourceFiles(srcDir);
|
|
271
|
+
const files = sourceFiles.map((f) => analyzeFile(f));
|
|
272
|
+
|
|
273
|
+
const totalWarnings = files.reduce((s, f) => s + f.warnings.length, 0);
|
|
274
|
+
const totalErrors = files.reduce((s, f) => s + f.errors.length, 0);
|
|
275
|
+
|
|
276
|
+
return {
|
|
277
|
+
files,
|
|
278
|
+
summary: {
|
|
279
|
+
totalFiles: files.length,
|
|
280
|
+
totalWarnings,
|
|
281
|
+
totalErrors,
|
|
282
|
+
},
|
|
283
|
+
};
|
|
284
|
+
}
|
|
285
|
+
|
|
286
|
+
/**
|
|
287
|
+
* Formats the performance report as a human-readable string.
|
|
288
|
+
*
|
|
289
|
+
* @param {PerformanceReport} report
|
|
290
|
+
* @returns {string}
|
|
291
|
+
*/
|
|
292
|
+
export function formatPerformanceReport(report) {
|
|
293
|
+
const lines = [];
|
|
294
|
+
|
|
295
|
+
lines.push('=== Performance Analysis Report ===');
|
|
296
|
+
lines.push('');
|
|
297
|
+
lines.push(`Files analyzed : ${report.summary.totalFiles}`);
|
|
298
|
+
lines.push(`Total warnings : ${report.summary.totalWarnings}`);
|
|
299
|
+
lines.push(`Total errors : ${report.summary.totalErrors}`);
|
|
300
|
+
lines.push('');
|
|
301
|
+
|
|
302
|
+
const filesWithIssues = report.files.filter(
|
|
303
|
+
(f) => f.warnings.length > 0 || f.errors.length > 0
|
|
304
|
+
);
|
|
305
|
+
|
|
306
|
+
if (filesWithIssues.length === 0) {
|
|
307
|
+
lines.push('No performance issues detected.');
|
|
308
|
+
return lines.join('\n');
|
|
309
|
+
}
|
|
310
|
+
|
|
311
|
+
for (const file of filesWithIssues) {
|
|
312
|
+
lines.push(`--- ${file.filePath} ---`);
|
|
313
|
+
lines.push(` Lines: ${file.lineCount} | Imports: ${file.importCount} | Complexity: ${file.complexity.cyclomaticComplexity} | Nesting: ${file.complexity.nestingDepth}`);
|
|
314
|
+
|
|
315
|
+
for (const err of file.errors) {
|
|
316
|
+
lines.push(` [ERR] ${err}`);
|
|
317
|
+
}
|
|
318
|
+
for (const warn of file.warnings) {
|
|
319
|
+
lines.push(` [WRN] ${warn}`);
|
|
320
|
+
}
|
|
321
|
+
lines.push('');
|
|
322
|
+
}
|
|
323
|
+
|
|
324
|
+
return lines.join('\n');
|
|
325
|
+
}
|