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.
Files changed (208) hide show
  1. package/README.md +40 -24
  2. package/framework/agents/build/dev.md +343 -0
  3. package/framework/agents/clarity/architect.md +112 -0
  4. package/framework/agents/clarity/brief.md +182 -0
  5. package/framework/agents/clarity/brownfield-wu.md +181 -0
  6. package/framework/agents/clarity/detail.md +110 -0
  7. package/framework/agents/clarity/greenfield-wu.md +153 -0
  8. package/framework/agents/clarity/ux.md +112 -0
  9. package/framework/config.yaml +3 -3
  10. package/framework/constitution.md +31 -1
  11. package/framework/context/governance.md +37 -0
  12. package/framework/context/protocols.md +34 -0
  13. package/framework/context/quality.md +27 -0
  14. package/framework/context/root.md +24 -0
  15. package/framework/data/entity-registry.yaml +1 -1
  16. package/framework/domains/agents/architect.yaml +51 -0
  17. package/framework/domains/agents/brief.yaml +47 -0
  18. package/framework/domains/agents/brownfield-wu.yaml +49 -0
  19. package/framework/domains/agents/detail.yaml +47 -0
  20. package/framework/domains/agents/dev.yaml +49 -0
  21. package/framework/domains/agents/devops.yaml +43 -0
  22. package/framework/domains/agents/greenfield-wu.yaml +47 -0
  23. package/framework/domains/agents/orchestrator.yaml +49 -0
  24. package/framework/domains/agents/phases.yaml +47 -0
  25. package/framework/domains/agents/qa-implementation.yaml +43 -0
  26. package/framework/domains/agents/qa-planning.yaml +44 -0
  27. package/framework/domains/agents/tasks.yaml +48 -0
  28. package/framework/domains/agents/ux.yaml +50 -0
  29. package/framework/domains/constitution.yaml +77 -0
  30. package/framework/domains/global.yaml +64 -0
  31. package/framework/domains/workflows/brownfield-discovery.yaml +16 -0
  32. package/framework/domains/workflows/brownfield-fullstack.yaml +26 -0
  33. package/framework/domains/workflows/brownfield-service.yaml +22 -0
  34. package/framework/domains/workflows/brownfield-ui.yaml +22 -0
  35. package/framework/domains/workflows/greenfield-fullstack.yaml +26 -0
  36. package/framework/hooks/constitution-guard.js +101 -0
  37. package/framework/hooks/mode-governance.js +92 -0
  38. package/framework/hooks/model-governance.js +76 -0
  39. package/framework/hooks/prism-engine.js +89 -0
  40. package/framework/hooks/session-digest.js +60 -0
  41. package/framework/hooks/settings.json +44 -0
  42. package/framework/i18n/en.yaml +3 -3
  43. package/framework/i18n/es.yaml +3 -3
  44. package/framework/i18n/fr.yaml +3 -3
  45. package/framework/i18n/pt.yaml +3 -3
  46. package/framework/intelligence/decision-engine.md +1 -1
  47. package/framework/migrations/v1.4-to-v2.0.yaml +167 -0
  48. package/framework/migrations/v2.0-to-v2.0.1.yaml +132 -0
  49. package/framework/orchestrator/chati.md +284 -6
  50. package/framework/tasks/architect-api-design.md +63 -0
  51. package/framework/tasks/architect-consolidate.md +47 -0
  52. package/framework/tasks/architect-db-design.md +73 -0
  53. package/framework/tasks/architect-design.md +95 -0
  54. package/framework/tasks/architect-security-review.md +62 -0
  55. package/framework/tasks/architect-stack-selection.md +53 -0
  56. package/framework/tasks/brief-consolidate.md +249 -0
  57. package/framework/tasks/brief-constraint-identify.md +277 -0
  58. package/framework/tasks/brief-extract-requirements.md +339 -0
  59. package/framework/tasks/brief-stakeholder-map.md +176 -0
  60. package/framework/tasks/brief-validate-completeness.md +121 -0
  61. package/framework/tasks/brownfield-wu-architecture-map.md +394 -0
  62. package/framework/tasks/brownfield-wu-deep-discovery.md +312 -0
  63. package/framework/tasks/brownfield-wu-dependency-scan.md +359 -0
  64. package/framework/tasks/brownfield-wu-migration-plan.md +483 -0
  65. package/framework/tasks/brownfield-wu-report.md +325 -0
  66. package/framework/tasks/brownfield-wu-risk-assess.md +424 -0
  67. package/framework/tasks/detail-acceptance-criteria.md +372 -0
  68. package/framework/tasks/detail-consolidate.md +138 -0
  69. package/framework/tasks/detail-edge-case-analysis.md +300 -0
  70. package/framework/tasks/detail-expand-prd.md +389 -0
  71. package/framework/tasks/detail-nfr-extraction.md +223 -0
  72. package/framework/tasks/dev-code-review.md +404 -0
  73. package/framework/tasks/dev-consolidate.md +543 -0
  74. package/framework/tasks/dev-debug.md +322 -0
  75. package/framework/tasks/dev-implement.md +252 -0
  76. package/framework/tasks/dev-iterate.md +411 -0
  77. package/framework/tasks/dev-pr-prepare.md +497 -0
  78. package/framework/tasks/dev-refactor.md +342 -0
  79. package/framework/tasks/dev-test-write.md +306 -0
  80. package/framework/tasks/devops-ci-setup.md +412 -0
  81. package/framework/tasks/devops-consolidate.md +712 -0
  82. package/framework/tasks/devops-deploy-config.md +598 -0
  83. package/framework/tasks/devops-monitoring-setup.md +658 -0
  84. package/framework/tasks/devops-release-prepare.md +673 -0
  85. package/framework/tasks/greenfield-wu-analyze-empty.md +169 -0
  86. package/framework/tasks/greenfield-wu-report.md +266 -0
  87. package/framework/tasks/greenfield-wu-scaffold-detection.md +203 -0
  88. package/framework/tasks/greenfield-wu-tech-stack-assess.md +255 -0
  89. package/framework/tasks/orchestrator-deviation.md +260 -0
  90. package/framework/tasks/orchestrator-escalate.md +276 -0
  91. package/framework/tasks/orchestrator-handoff.md +243 -0
  92. package/framework/tasks/orchestrator-health.md +372 -0
  93. package/framework/tasks/orchestrator-mode-switch.md +262 -0
  94. package/framework/tasks/orchestrator-resume.md +189 -0
  95. package/framework/tasks/orchestrator-route.md +169 -0
  96. package/framework/tasks/orchestrator-spawn-terminal.md +358 -0
  97. package/framework/tasks/orchestrator-status.md +260 -0
  98. package/framework/tasks/orchestrator-suggest-mode.md +372 -0
  99. package/framework/tasks/phases-breakdown.md +91 -0
  100. package/framework/tasks/phases-dependency-mapping.md +67 -0
  101. package/framework/tasks/phases-mvp-scoping.md +94 -0
  102. package/framework/tasks/qa-impl-consolidate.md +522 -0
  103. package/framework/tasks/qa-impl-performance-test.md +487 -0
  104. package/framework/tasks/qa-impl-regression-check.md +413 -0
  105. package/framework/tasks/qa-impl-sast-scan.md +402 -0
  106. package/framework/tasks/qa-impl-test-execute.md +344 -0
  107. package/framework/tasks/qa-impl-verdict.md +339 -0
  108. package/framework/tasks/qa-planning-consolidate.md +309 -0
  109. package/framework/tasks/qa-planning-coverage-plan.md +338 -0
  110. package/framework/tasks/qa-planning-gate-define.md +339 -0
  111. package/framework/tasks/qa-planning-risk-matrix.md +631 -0
  112. package/framework/tasks/qa-planning-test-strategy.md +217 -0
  113. package/framework/tasks/tasks-acceptance-write.md +75 -0
  114. package/framework/tasks/tasks-consolidate.md +57 -0
  115. package/framework/tasks/tasks-decompose.md +80 -0
  116. package/framework/tasks/tasks-estimate.md +66 -0
  117. package/framework/tasks/ux-a11y-check.md +49 -0
  118. package/framework/tasks/ux-component-map.md +55 -0
  119. package/framework/tasks/ux-consolidate.md +46 -0
  120. package/framework/tasks/ux-user-flow.md +46 -0
  121. package/framework/tasks/ux-wireframe.md +76 -0
  122. package/package.json +2 -2
  123. package/scripts/bundle-framework.js +2 -0
  124. package/scripts/changelog-generator.js +222 -0
  125. package/scripts/codebase-mapper.js +728 -0
  126. package/scripts/commit-message-generator.js +167 -0
  127. package/scripts/coverage-analyzer.js +260 -0
  128. package/scripts/dependency-analyzer.js +280 -0
  129. package/scripts/framework-analyzer.js +308 -0
  130. package/scripts/generate-constitution-domain.js +253 -0
  131. package/scripts/health-check.js +481 -0
  132. package/scripts/ide-sync.js +327 -0
  133. package/scripts/performance-analyzer.js +325 -0
  134. package/scripts/plan-tracker.js +278 -0
  135. package/scripts/populate-entity-registry.js +481 -0
  136. package/scripts/pr-review.js +317 -0
  137. package/scripts/rollback-manager.js +310 -0
  138. package/scripts/stuck-detector.js +343 -0
  139. package/scripts/test-quality-assessment.js +257 -0
  140. package/scripts/validate-agents.js +367 -0
  141. package/scripts/validate-tasks.js +465 -0
  142. package/src/autonomy/autonomous-gate.js +293 -0
  143. package/src/autonomy/index.js +51 -0
  144. package/src/autonomy/mode-manager.js +225 -0
  145. package/src/autonomy/mode-suggester.js +283 -0
  146. package/src/autonomy/progress-reporter.js +268 -0
  147. package/src/autonomy/safety-net.js +320 -0
  148. package/src/context/bracket-tracker.js +79 -0
  149. package/src/context/domain-loader.js +107 -0
  150. package/src/context/engine.js +144 -0
  151. package/src/context/formatter.js +184 -0
  152. package/src/context/index.js +4 -0
  153. package/src/context/layers/l0-constitution.js +28 -0
  154. package/src/context/layers/l1-global.js +37 -0
  155. package/src/context/layers/l2-agent.js +39 -0
  156. package/src/context/layers/l3-workflow.js +42 -0
  157. package/src/context/layers/l4-task.js +24 -0
  158. package/src/decision/analyzer.js +167 -0
  159. package/src/decision/engine.js +270 -0
  160. package/src/decision/index.js +38 -0
  161. package/src/decision/registry-healer.js +450 -0
  162. package/src/decision/registry-updater.js +330 -0
  163. package/src/gates/circuit-breaker.js +119 -0
  164. package/src/gates/g1-planning-complete.js +153 -0
  165. package/src/gates/g2-qa-planning.js +153 -0
  166. package/src/gates/g3-implementation.js +188 -0
  167. package/src/gates/g4-qa-implementation.js +207 -0
  168. package/src/gates/g5-deploy-ready.js +180 -0
  169. package/src/gates/gate-base.js +144 -0
  170. package/src/gates/index.js +46 -0
  171. package/src/installer/brownfield-upgrader.js +249 -0
  172. package/src/installer/core.js +82 -11
  173. package/src/installer/file-hasher.js +51 -0
  174. package/src/installer/manifest.js +117 -0
  175. package/src/installer/templates.js +17 -15
  176. package/src/installer/transaction.js +229 -0
  177. package/src/installer/validator.js +18 -1
  178. package/src/intelligence/registry-manager.js +2 -2
  179. package/src/memory/agent-memory.js +255 -0
  180. package/src/memory/gotchas-injector.js +72 -0
  181. package/src/memory/gotchas.js +361 -0
  182. package/src/memory/index.js +35 -0
  183. package/src/memory/search.js +233 -0
  184. package/src/memory/session-digest.js +239 -0
  185. package/src/merger/env-merger.js +112 -0
  186. package/src/merger/index.js +56 -0
  187. package/src/merger/replace-merger.js +51 -0
  188. package/src/merger/yaml-merger.js +127 -0
  189. package/src/orchestrator/agent-selector.js +285 -0
  190. package/src/orchestrator/deviation-handler.js +350 -0
  191. package/src/orchestrator/handoff-engine.js +271 -0
  192. package/src/orchestrator/index.js +67 -0
  193. package/src/orchestrator/intent-classifier.js +264 -0
  194. package/src/orchestrator/pipeline-manager.js +492 -0
  195. package/src/orchestrator/pipeline-state.js +223 -0
  196. package/src/orchestrator/session-manager.js +409 -0
  197. package/src/tasks/executor.js +195 -0
  198. package/src/tasks/handoff.js +226 -0
  199. package/src/tasks/index.js +4 -0
  200. package/src/tasks/loader.js +210 -0
  201. package/src/tasks/router.js +182 -0
  202. package/src/terminal/collector.js +216 -0
  203. package/src/terminal/index.js +30 -0
  204. package/src/terminal/isolation.js +129 -0
  205. package/src/terminal/monitor.js +277 -0
  206. package/src/terminal/spawner.js +269 -0
  207. package/src/upgrade/checker.js +1 -1
  208. 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
+ }