chati-dev 4.2.2 → 4.3.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (221) hide show
  1. package/README.md +80 -53
  2. package/bin/chati.js +170 -5
  3. package/framework/agents/build/dev.md +509 -76
  4. package/framework/agents/deploy/devops.md +40 -25
  5. package/framework/agents/discover/brief.md +158 -22
  6. package/framework/agents/discover/brownfield-wu.md +26 -14
  7. package/framework/agents/discover/greenfield-wu.md +102 -14
  8. package/framework/agents/plan/architect-data-engineer.md +6 -6
  9. package/framework/agents/plan/architect-system.md +46 -12
  10. package/framework/agents/plan/architect.md +40 -20
  11. package/framework/agents/plan/detail.md +38 -24
  12. package/framework/agents/plan/phases.md +36 -26
  13. package/framework/agents/plan/tasks.md +114 -33
  14. package/framework/agents/plan/ux-brand-architect.md +240 -8
  15. package/framework/agents/plan/ux-component-engineer.md +28 -13
  16. package/framework/agents/plan/ux-researcher.md +7 -6
  17. package/framework/agents/plan/ux.md +55 -22
  18. package/framework/agents/quality/qa-implementation.md +143 -74
  19. package/framework/agents/quality/qa-planning.md +115 -42
  20. package/framework/agents/quality/qa-visual.md +448 -0
  21. package/framework/agents/shared/visualizer.md +128 -0
  22. package/framework/config.yaml +7 -6
  23. package/framework/constitution.md +127 -44
  24. package/framework/context/governance.md +12 -7
  25. package/framework/context/quality.md +6 -5
  26. package/framework/context/root.md +6 -6
  27. package/framework/data/entity-registry.yaml +377 -4
  28. package/framework/data/model-limits.json +19 -0
  29. package/framework/domains/agents/qa-visual.yaml +74 -0
  30. package/framework/domains/constitution.yaml +46 -2
  31. package/framework/domains/workflows/greenfield-fullstack.yaml +2 -2
  32. package/framework/hooks/advance-trigger.js +131 -0
  33. package/framework/hooks/brief-validator.js +83 -0
  34. package/framework/hooks/constitution-guard.js +24 -5
  35. package/framework/hooks/license-guard.js +62 -27
  36. package/framework/hooks/mode-governance.js +13 -2
  37. package/framework/hooks/model-governance.js +1 -0
  38. package/framework/hooks/post-dev.js +207 -0
  39. package/framework/hooks/prism-engine.js +274 -105
  40. package/framework/hooks/reasoning-escalator.js +371 -0
  41. package/framework/hooks/reference-trigger.js +117 -0
  42. package/framework/hooks/session-digest.js +50 -1
  43. package/framework/hooks/settings.json +32 -1
  44. package/framework/hooks/style-guard.js +25 -6
  45. package/framework/hooks/team-quality-gate.js +19 -12
  46. package/framework/hooks/undercover-guard.js +4 -2
  47. package/framework/i18n/en.yaml +3 -3
  48. package/framework/i18n/es.yaml +3 -3
  49. package/framework/i18n/fr.yaml +3 -3
  50. package/framework/i18n/pt.yaml +3 -3
  51. package/framework/intelligence/context-engine.md +4 -5
  52. package/framework/intelligence/decision-engine.md +1 -1
  53. package/framework/intelligence/hooks-performance.md +3 -3
  54. package/framework/migrations/v1.0-to-v1.1.yaml +1 -1
  55. package/framework/migrations/v1.4-to-v2.0.yaml +11 -11
  56. package/framework/migrations/v4.0-to-v4.1.yaml +2 -2
  57. package/framework/migrations/v4.2-to-v4.3.yaml +29 -0
  58. package/framework/orchestrator/chati-router.js +441 -0
  59. package/framework/orchestrator/chati-update.md +40 -40
  60. package/framework/orchestrator/chati.md +295 -94
  61. package/framework/rules/human-writing-style.md +47 -0
  62. package/framework/scaffold/motion-premium/README.md +99 -0
  63. package/framework/scaffold/motion-premium/app/globals.css.template +400 -0
  64. package/framework/scaffold/motion-premium/app/layout.tsx.template +110 -0
  65. package/framework/scaffold/motion-premium/components/animation/BackgroundCrossfadeProvider.tsx.template +170 -0
  66. package/framework/scaffold/motion-premium/components/animation/LenisProvider.tsx.template +49 -0
  67. package/framework/scaffold/motion-premium/components/animation/PageTransitionWrapper.tsx.template +83 -0
  68. package/framework/scaffold/motion-premium/components/animation/Preloader.tsx.template +171 -0
  69. package/framework/scaffold/motion-premium/components/ui/Container.tsx.template +69 -0
  70. package/framework/scaffold/motion-premium/components/ui/PageSection.tsx.template +74 -0
  71. package/framework/scaffold/motion-premium/lib/animations/gsap.ts.template +112 -0
  72. package/framework/scaffold/motion-premium/lib/animations/refreshCoordinator.ts.template +75 -0
  73. package/framework/scaffold/motion-premium/lib/animations/tokens.ts.template +119 -0
  74. package/framework/scaffold/motion-premium/lib/animations/useGsapContext.ts.template +92 -0
  75. package/framework/scaffold/motion-premium/lib/animations/useScrollSnapStepper.ts.template +265 -0
  76. package/framework/scaffold/motion-premium/lib/animations/useSmoothScroll.ts.template +67 -0
  77. package/framework/scaffold/motion-premium/lib/brand.ts.template +43 -0
  78. package/framework/scaffold/motion-premium/scaffold.yaml +178 -0
  79. package/framework/scaffold/motion-premium-3d/README.md +90 -0
  80. package/framework/scaffold/motion-premium-3d/app/(3d)/scroll-demo/ScrollDemoCanvas.tsx.template +81 -0
  81. package/framework/scaffold/motion-premium-3d/app/(3d)/scroll-demo/ScrollDemoClient.tsx.template +75 -0
  82. package/framework/scaffold/motion-premium-3d/app/(3d)/scroll-demo/page.tsx.template +26 -0
  83. package/framework/scaffold/motion-premium-3d/components/3d/CameraRig.tsx.template +100 -0
  84. package/framework/scaffold/motion-premium-3d/components/3d/CanvasProvider.tsx.template +85 -0
  85. package/framework/scaffold/motion-premium-3d/components/3d/InvalidateOnScroll.tsx.template +51 -0
  86. package/framework/scaffold/motion-premium-3d/components/3d/MeshCrossfade.tsx.template +79 -0
  87. package/framework/scaffold/motion-premium-3d/components/3d/ScrollCrossfade.tsx.template +88 -0
  88. package/framework/scaffold/motion-premium-3d/components/3d/ScrollScene.tsx.template +121 -0
  89. package/framework/scaffold/motion-premium-3d/components/webgl/SceneFallback.tsx.template +65 -0
  90. package/framework/scaffold/motion-premium-3d/components/webgl/WebGLContext.tsx.template +68 -0
  91. package/framework/scaffold/motion-premium-3d/lib/webgl/detect.ts.template +69 -0
  92. package/framework/scaffold/motion-premium-3d/scaffold.yaml +136 -0
  93. package/framework/schemas/session.schema.json +109 -21
  94. package/framework/scripts/reference-capture.js +430 -0
  95. package/framework/scripts/visual-qa.js +686 -0
  96. package/framework/tasks/orchestrator-handoff.md +1 -1
  97. package/framework/tasks/orchestrator-resume.md +1 -1
  98. package/framework/tasks/orchestrator-route.md +1 -1
  99. package/framework/tasks/orchestrator-status.md +3 -3
  100. package/framework/tasks/qa-planning-gate-define.md +1 -1
  101. package/framework/templates/brandbook-html-tmpl.md +2 -2
  102. package/framework/templates/brandbook-tmpl.yaml +1 -1
  103. package/framework/templates/component-spec-tmpl.yaml +1 -1
  104. package/framework/templates/design-token-tmpl.yaml +1 -1
  105. package/framework/templates/icon-system-tmpl.yaml +1 -1
  106. package/framework/templates/team-planning-tasks.yaml +6 -5
  107. package/framework/workflows/brownfield-discovery.yaml +2 -2
  108. package/framework/workflows/brownfield-fullstack.yaml +15 -11
  109. package/framework/workflows/brownfield-service.yaml +14 -10
  110. package/framework/workflows/brownfield-ui.yaml +15 -11
  111. package/framework/workflows/greenfield-fullstack.yaml +16 -13
  112. package/framework/workflows/quick-flow.yaml +3 -3
  113. package/framework/workflows/standard-flow.yaml +12 -9
  114. package/package.json +12 -6
  115. package/src/autonomy/autonomous-gate.js +1 -0
  116. package/src/autonomy/build-state.js +1 -2
  117. package/src/autonomy/progress-reporter.js +1 -1
  118. package/src/config/agent-customizer.js +11 -3
  119. package/src/config/claude-settings-generator.js +27 -7
  120. package/src/config/context-file-generator.js +41 -21
  121. package/src/config/framework-adapter.js +1 -0
  122. package/src/config/gemini-hooks-generator.js +19 -7
  123. package/src/config/mcp-configs.js +1 -0
  124. package/src/context/layers/l1-global.js +2 -1
  125. package/src/dashboard/data-reader.js +4 -3
  126. package/src/dashboard/layout.js +2 -1
  127. package/src/decision/analyzer.js +6 -30
  128. package/src/decision/engine.js +4 -28
  129. package/src/decision/registry-healer.js +3 -2
  130. package/src/decision/registry-updater.js +23 -14
  131. package/src/extensions/loader.js +2 -8
  132. package/src/gates/g1-planning-complete.js +2 -1
  133. package/src/gates/g2-qa-planning.js +2 -1
  134. package/src/gates/g3-implementation.js +2 -1
  135. package/src/gates/g4-qa-implementation.js +3 -2
  136. package/src/gates/g5-deploy-ready.js +2 -1
  137. package/src/health/engine.js +4 -3
  138. package/src/installer/core.js +431 -81
  139. package/src/installer/preflight.js +131 -0
  140. package/src/installer/provider-overlay.js +3 -3
  141. package/src/installer/scaffold-applier.js +358 -0
  142. package/src/installer/templates.js +46 -29
  143. package/src/installer/validator.js +17 -12
  144. package/src/intelligence/registry-manager.js +22 -29
  145. package/src/intelligence/timeline.js +11 -6
  146. package/src/license/commands.js +1 -1
  147. package/src/license/wait.js +102 -0
  148. package/src/memory/agent-memory.js +81 -0
  149. package/src/memory/dream.js +32 -1
  150. package/src/merger/replace-merger.js +28 -15
  151. package/src/orchestrator/agent-selector.js +2 -1
  152. package/src/orchestrator/cli.js +1880 -71
  153. package/src/orchestrator/doctor.js +270 -0
  154. package/src/orchestrator/handoff-engine.js +4 -3
  155. package/src/orchestrator/index.js +2 -0
  156. package/src/orchestrator/pipeline-manager.js +306 -15
  157. package/src/orchestrator/session-manager.js +339 -6
  158. package/src/tasks/handoff.js +3 -2
  159. package/src/telemetry/config.js +4 -3
  160. package/src/telemetry/schema.js +1 -0
  161. package/src/terminal/collector.js +3 -2
  162. package/src/terminal/index.js +1 -2
  163. package/src/terminal/isolation.js +52 -18
  164. package/src/terminal/prompt-builder.js +42 -25
  165. package/src/terminal/run-parallel.js +1 -1
  166. package/src/terminal/run-team.js +3 -3
  167. package/src/terminal/team-task-list.js +43 -4
  168. package/src/upgrade/backup.js +3 -2
  169. package/src/upgrade/checker.js +3 -2
  170. package/src/upgrade/migrator.js +65 -7
  171. package/src/upgrade/tracked-files-detector.js +86 -0
  172. package/src/upgrade/user-messages.js +94 -0
  173. package/src/utils/config-parser.js +2 -1
  174. package/src/utils/feature-flags.js +2 -1
  175. package/src/utils/flatten-entities.js +69 -0
  176. package/src/utils/framework-dir.js +16 -0
  177. package/src/utils/model-id.js +85 -0
  178. package/src/utils/provider-limits.js +84 -23
  179. package/src/utils/schema-validator.js +1 -1
  180. package/src/wizard/i18n.js +5 -4
  181. package/src/wizard/index.js +14 -0
  182. package/assets/logo - c/303/263pia.png +0 -0
  183. package/assets/logo.svg +0 -42
  184. package/assets/logo2.png +0 -0
  185. package/assets/social-preview.png +0 -0
  186. package/scripts/bundle-framework.js +0 -69
  187. package/scripts/changelog-generator.js +0 -222
  188. package/scripts/codebase-mapper.js +0 -728
  189. package/scripts/commit-message-generator.js +0 -167
  190. package/scripts/coverage-analyzer.js +0 -260
  191. package/scripts/dependency-analyzer.js +0 -280
  192. package/scripts/doctor/checks/agents.js +0 -77
  193. package/scripts/doctor/checks/constitution.js +0 -41
  194. package/scripts/doctor/checks/domain-alignment.js +0 -58
  195. package/scripts/doctor/checks/prism-layers.js +0 -84
  196. package/scripts/doctor/checks/registry.js +0 -55
  197. package/scripts/doctor/checks/schemas.js +0 -61
  198. package/scripts/doctor/fixes/reference-fix.js +0 -100
  199. package/scripts/doctor/fixes/registry-fix.js +0 -56
  200. package/scripts/doctor/index.js +0 -212
  201. package/scripts/framework-analyzer.js +0 -308
  202. package/scripts/generate-constitution-domain.js +0 -253
  203. package/scripts/generate-signing-key.js +0 -33
  204. package/scripts/health-check.js +0 -481
  205. package/scripts/ide-sync.js +0 -326
  206. package/scripts/performance-analyzer.js +0 -325
  207. package/scripts/plan-tracker.js +0 -278
  208. package/scripts/populate-entity-registry.js +0 -481
  209. package/scripts/pr-review.js +0 -317
  210. package/scripts/rollback-manager.js +0 -310
  211. package/scripts/semantic-lint.js +0 -328
  212. package/scripts/sign-manifest.js +0 -53
  213. package/scripts/stuck-detector.js +0 -343
  214. package/scripts/test-quality-assessment.js +0 -257
  215. package/scripts/validate-agents.js +0 -368
  216. package/scripts/validate-package.js +0 -505
  217. package/scripts/validate-tasks.js +0 -465
  218. package/src/autonomy/worktree-manager.js +0 -250
  219. package/src/intelligence/decision-engine.js +0 -256
  220. package/src/intelligence/document-sharder.js +0 -221
  221. package/src/intelligence/elicitation.js +0 -265
@@ -1,728 +0,0 @@
1
- #!/usr/bin/env node
2
-
3
- /**
4
- * Codebase Mapper — Scans a target project and generates a structured map.
5
- *
6
- * Exports:
7
- * mapCodebase(targetDir) → CodebaseMap
8
- * saveCodebaseMap(targetDir, map) → saves to .chati/codebase-map.json
9
- * loadCodebaseMap(targetDir) → reads from .chati/codebase-map.json
10
- *
11
- * Internal helpers:
12
- * scanDirectory(dir, depth, maxDepth) → recursive scanner
13
- * detectConventions(files) → naming patterns
14
- * detectBuildTools(targetDir) → vite/webpack/esbuild/etc
15
- * detectTestFramework(targetDir) → jest/vitest/mocha/node:test
16
- * parseDependencies(targetDir) → from package.json
17
- * guessDirectoryPurpose(dirName, contents) → 'source'|'test'|'config'|'docs'|etc
18
- */
19
-
20
- import { readFileSync, writeFileSync, readdirSync, statSync, existsSync, mkdirSync } from 'fs';
21
- import { join, extname, basename } from 'path';
22
-
23
- /**
24
- * Directories to always ignore during scanning.
25
- */
26
- const IGNORE_DIRS = new Set([
27
- 'node_modules', '.git', 'dist', 'build', 'coverage',
28
- '.chati', '.next', '.nuxt', '.cache', '.turbo',
29
- '__pycache__', '.venv', 'venv', '.tox',
30
- 'vendor', 'target', 'out', '.output',
31
- ]);
32
-
33
- const IGNORE_FILES = new Set([
34
- '.DS_Store', 'Thumbs.db', '.gitkeep',
35
- ]);
36
-
37
- /**
38
- * Maximum depth for recursive directory scanning.
39
- */
40
- const MAX_DEPTH = 10;
41
-
42
- /**
43
- * Maximum number of files to scan before stopping (safety limit).
44
- */
45
- const MAX_FILES = 50000;
46
-
47
- // ---------------------------------------------------------------------------
48
- // Internal Helpers
49
- // ---------------------------------------------------------------------------
50
-
51
- /**
52
- * Guess the purpose of a directory based on its name and contents.
53
- * @param {string} dirName - The directory basename.
54
- * @param {string[]} contents - Filenames inside the directory.
55
- * @returns {string} Purpose label.
56
- */
57
- export function guessDirectoryPurpose(dirName, contents = []) {
58
- const name = dirName.toLowerCase();
59
-
60
- // Direct name matches
61
- const purposeMap = {
62
- src: 'source',
63
- source: 'source',
64
- lib: 'source',
65
- app: 'source',
66
- pages: 'source',
67
- components: 'source',
68
- modules: 'source',
69
-
70
- test: 'test',
71
- tests: 'test',
72
- __tests__: 'test',
73
- spec: 'test',
74
- specs: 'test',
75
- e2e: 'test',
76
- cypress: 'test',
77
-
78
- docs: 'docs',
79
- doc: 'docs',
80
- documentation: 'docs',
81
- wiki: 'docs',
82
-
83
- config: 'config',
84
- configs: 'config',
85
- configuration: 'config',
86
-
87
- scripts: 'scripts',
88
- bin: 'scripts',
89
- tools: 'scripts',
90
-
91
- assets: 'assets',
92
- static: 'assets',
93
- public: 'assets',
94
- images: 'assets',
95
- img: 'assets',
96
- fonts: 'assets',
97
- media: 'assets',
98
-
99
- styles: 'styles',
100
- css: 'styles',
101
- scss: 'styles',
102
-
103
- types: 'types',
104
- typings: 'types',
105
- '@types': 'types',
106
-
107
- utils: 'utilities',
108
- helpers: 'utilities',
109
- shared: 'utilities',
110
- common: 'utilities',
111
-
112
- api: 'api',
113
- routes: 'api',
114
- controllers: 'api',
115
- endpoints: 'api',
116
-
117
- models: 'data',
118
- schemas: 'data',
119
- entities: 'data',
120
- migrations: 'data',
121
- database: 'data',
122
- db: 'data',
123
-
124
- middleware: 'middleware',
125
- middlewares: 'middleware',
126
-
127
- hooks: 'hooks',
128
-
129
- services: 'services',
130
- providers: 'services',
131
-
132
- store: 'state',
133
- stores: 'state',
134
- state: 'state',
135
- redux: 'state',
136
- context: 'state',
137
-
138
- i18n: 'i18n',
139
- locales: 'i18n',
140
- lang: 'i18n',
141
- translations: 'i18n',
142
-
143
- '.github': 'ci',
144
- '.circleci': 'ci',
145
- '.gitlab': 'ci',
146
-
147
- '.vscode': 'ide-config',
148
- '.idea': 'ide-config',
149
- '.cursor': 'ide-config',
150
- '.windsurf': 'ide-config',
151
- };
152
-
153
- if (purposeMap[name]) return purposeMap[name];
154
-
155
- // Content-based guessing
156
- const hasTestFiles = contents.some(f => /\.(test|spec)\.[jt]sx?$/.test(f));
157
- if (hasTestFiles) return 'test';
158
-
159
- const hasConfigFiles = contents.some(f =>
160
- /\.(config|rc)\.[jt]s$/.test(f) || /\.ya?ml$/.test(f) || /\.json$/.test(f),
161
- );
162
- if (hasConfigFiles && contents.length <= 5) return 'config';
163
-
164
- return 'unknown';
165
- }
166
-
167
- /**
168
- * Recursively scan a directory and collect file/directory information.
169
- * @param {string} dir - Directory to scan.
170
- * @param {number} depth - Current depth.
171
- * @param {number} maxDepth - Maximum recursion depth.
172
- * @param {object} acc - Accumulator for results.
173
- * @returns {object} Directory tree node.
174
- */
175
- export function scanDirectory(dir, depth = 0, maxDepth = MAX_DEPTH, acc = { fileCount: 0 }) {
176
- if (depth > maxDepth || acc.fileCount > MAX_FILES) {
177
- return { name: basename(dir), type: 'directory', depth, truncated: true, children: [] };
178
- }
179
-
180
- let entries;
181
- try {
182
- entries = readdirSync(dir, { withFileTypes: true });
183
- } catch {
184
- return { name: basename(dir), type: 'directory', depth, error: 'unreadable', children: [] };
185
- }
186
-
187
- const children = [];
188
- let fileCount = 0;
189
-
190
- for (const entry of entries) {
191
- if (IGNORE_FILES.has(entry.name)) continue;
192
-
193
- if (entry.isDirectory()) {
194
- if (IGNORE_DIRS.has(entry.name)) continue;
195
- const child = scanDirectory(join(dir, entry.name), depth + 1, maxDepth, acc);
196
- children.push(child);
197
- } else if (entry.isFile()) {
198
- fileCount++;
199
- acc.fileCount++;
200
- children.push({
201
- name: entry.name,
202
- type: 'file',
203
- extension: extname(entry.name),
204
- depth: depth + 1,
205
- });
206
- }
207
- }
208
-
209
- const dirContents = entries.filter(e => e.isFile()).map(e => e.name);
210
- const purpose = guessDirectoryPurpose(basename(dir), dirContents);
211
-
212
- return {
213
- name: basename(dir),
214
- type: 'directory',
215
- depth,
216
- purpose,
217
- fileCount,
218
- childDirCount: children.filter(c => c.type === 'directory').length,
219
- children,
220
- };
221
- }
222
-
223
- /**
224
- * Detect naming conventions from a list of files.
225
- * @param {string[]} files - Flat list of filenames.
226
- * @returns {object} Convention analysis.
227
- */
228
- export function detectConventions(files) {
229
- const conventions = {
230
- naming: { camelCase: 0, kebabCase: 0, snakeCase: 0, pascalCase: 0 },
231
- testPattern: null,
232
- configPattern: null,
233
- };
234
-
235
- for (const file of files) {
236
- const name = basename(file, extname(file));
237
- if (/^[a-z][a-zA-Z0-9]*$/.test(name) && /[A-Z]/.test(name)) {
238
- conventions.naming.camelCase++;
239
- } else if (/^[a-z][a-z0-9]*(-[a-z0-9]+)+$/.test(name)) {
240
- conventions.naming.kebabCase++;
241
- } else if (/^[a-z][a-z0-9]*(_[a-z0-9]+)+$/.test(name)) {
242
- conventions.naming.snakeCase++;
243
- } else if (/^[A-Z][a-zA-Z0-9]*$/.test(name)) {
244
- conventions.naming.pascalCase++;
245
- }
246
- }
247
-
248
- // Detect dominant naming convention
249
- const counts = conventions.naming;
250
- const max = Math.max(counts.camelCase, counts.kebabCase, counts.snakeCase, counts.pascalCase);
251
- if (max > 0) {
252
- if (counts.camelCase === max) conventions.dominant = 'camelCase';
253
- else if (counts.kebabCase === max) conventions.dominant = 'kebab-case';
254
- else if (counts.snakeCase === max) conventions.dominant = 'snake_case';
255
- else conventions.dominant = 'PascalCase';
256
- } else {
257
- conventions.dominant = 'unknown';
258
- }
259
-
260
- // Detect test naming pattern
261
- const testFiles = files.filter(f => /\.(test|spec)\.[jt]sx?$/.test(f));
262
- if (testFiles.length > 0) {
263
- const hasTest = testFiles.some(f => f.includes('.test.'));
264
- const hasSpec = testFiles.some(f => f.includes('.spec.'));
265
- conventions.testPattern = hasTest && hasSpec ? 'mixed' : hasTest ? '.test.ext' : '.spec.ext';
266
- }
267
-
268
- return conventions;
269
- }
270
-
271
- /**
272
- * Detect build tools present in the target directory.
273
- * @param {string} targetDir - Project root.
274
- * @returns {string[]} List of detected build tools.
275
- */
276
- export function detectBuildTools(targetDir) {
277
- const tools = [];
278
- const checks = [
279
- { file: 'vite.config.js', tool: 'vite' },
280
- { file: 'vite.config.ts', tool: 'vite' },
281
- { file: 'vite.config.mjs', tool: 'vite' },
282
- { file: 'webpack.config.js', tool: 'webpack' },
283
- { file: 'webpack.config.ts', tool: 'webpack' },
284
- { file: 'rollup.config.js', tool: 'rollup' },
285
- { file: 'rollup.config.mjs', tool: 'rollup' },
286
- { file: 'esbuild.config.js', tool: 'esbuild' },
287
- { file: 'tsconfig.json', tool: 'typescript' },
288
- { file: 'jsconfig.json', tool: 'jsconfig' },
289
- { file: 'next.config.js', tool: 'next' },
290
- { file: 'next.config.mjs', tool: 'next' },
291
- { file: 'nuxt.config.ts', tool: 'nuxt' },
292
- { file: 'nuxt.config.js', tool: 'nuxt' },
293
- { file: 'astro.config.mjs', tool: 'astro' },
294
- { file: 'svelte.config.js', tool: 'svelte' },
295
- { file: 'angular.json', tool: 'angular' },
296
- { file: 'turbo.json', tool: 'turborepo' },
297
- { file: 'nx.json', tool: 'nx' },
298
- { file: 'Makefile', tool: 'make' },
299
- { file: 'Cargo.toml', tool: 'cargo' },
300
- { file: 'go.mod', tool: 'go' },
301
- { file: 'pom.xml', tool: 'maven' },
302
- { file: 'build.gradle', tool: 'gradle' },
303
- { file: 'pyproject.toml', tool: 'pyproject' },
304
- { file: 'setup.py', tool: 'setuptools' },
305
- ];
306
-
307
- const seen = new Set();
308
- for (const { file, tool } of checks) {
309
- if (!seen.has(tool) && existsSync(join(targetDir, file))) {
310
- tools.push(tool);
311
- seen.add(tool);
312
- }
313
- }
314
-
315
- return tools;
316
- }
317
-
318
- /**
319
- * Detect the test framework used in the project.
320
- * @param {string} targetDir - Project root.
321
- * @returns {string[]} Detected test frameworks.
322
- */
323
- export function detectTestFramework(targetDir) {
324
- const frameworks = [];
325
- const pkgPath = join(targetDir, 'package.json');
326
-
327
- if (existsSync(pkgPath)) {
328
- try {
329
- const pkg = JSON.parse(readFileSync(pkgPath, 'utf8'));
330
- const allDeps = {
331
- ...pkg.dependencies,
332
- ...pkg.devDependencies,
333
- };
334
-
335
- if (allDeps.jest) frameworks.push('jest');
336
- if (allDeps.vitest) frameworks.push('vitest');
337
- if (allDeps.mocha) frameworks.push('mocha');
338
- if (allDeps.ava) frameworks.push('ava');
339
- if (allDeps.tap) frameworks.push('tap');
340
- if (allDeps.cypress) frameworks.push('cypress');
341
- if (allDeps.playwright || allDeps['@playwright/test']) frameworks.push('playwright');
342
-
343
- // Check scripts for node:test usage
344
- const scripts = pkg.scripts || {};
345
- const testScript = scripts.test || '';
346
- if (testScript.includes('node --test') || testScript.includes('node:test')) {
347
- frameworks.push('node:test');
348
- }
349
- } catch {
350
- // Invalid JSON, skip
351
- }
352
- }
353
-
354
- // Check for config files
355
- const configChecks = [
356
- { file: 'jest.config.js', fw: 'jest' },
357
- { file: 'jest.config.ts', fw: 'jest' },
358
- { file: 'vitest.config.js', fw: 'vitest' },
359
- { file: 'vitest.config.ts', fw: 'vitest' },
360
- { file: '.mocharc.yml', fw: 'mocha' },
361
- { file: '.mocharc.json', fw: 'mocha' },
362
- { file: 'cypress.config.js', fw: 'cypress' },
363
- { file: 'cypress.config.ts', fw: 'cypress' },
364
- { file: 'playwright.config.ts', fw: 'playwright' },
365
- { file: 'playwright.config.js', fw: 'playwright' },
366
- ];
367
-
368
- for (const { file, fw } of configChecks) {
369
- if (!frameworks.includes(fw) && existsSync(join(targetDir, file))) {
370
- frameworks.push(fw);
371
- }
372
- }
373
-
374
- return [...new Set(frameworks)];
375
- }
376
-
377
- /**
378
- * Parse dependencies from package.json.
379
- * @param {string} targetDir - Project root.
380
- * @returns {object} Dependencies info.
381
- */
382
- export function parseDependencies(targetDir) {
383
- const pkgPath = join(targetDir, 'package.json');
384
- const result = {
385
- packageManager: 'unknown',
386
- dependencies: {},
387
- devDependencies: {},
388
- totalDeps: 0,
389
- totalDevDeps: 0,
390
- };
391
-
392
- if (!existsSync(pkgPath)) return result;
393
-
394
- try {
395
- const pkg = JSON.parse(readFileSync(pkgPath, 'utf8'));
396
- result.dependencies = pkg.dependencies || {};
397
- result.devDependencies = pkg.devDependencies || {};
398
- result.totalDeps = Object.keys(result.dependencies).length;
399
- result.totalDevDeps = Object.keys(result.devDependencies).length;
400
-
401
- // Detect package manager
402
- if (existsSync(join(targetDir, 'pnpm-lock.yaml'))) result.packageManager = 'pnpm';
403
- else if (existsSync(join(targetDir, 'yarn.lock'))) result.packageManager = 'yarn';
404
- else if (existsSync(join(targetDir, 'bun.lockb'))) result.packageManager = 'bun';
405
- else if (existsSync(join(targetDir, 'package-lock.json'))) result.packageManager = 'npm';
406
-
407
- if (pkg.name) result.name = pkg.name;
408
- if (pkg.version) result.version = pkg.version;
409
- if (pkg.type) result.moduleType = pkg.type;
410
- if (pkg.engines) result.engines = pkg.engines;
411
- } catch {
412
- // Invalid JSON
413
- }
414
-
415
- // Also check for Python
416
- const reqPath = join(targetDir, 'requirements.txt');
417
- if (existsSync(reqPath)) {
418
- try {
419
- const content = readFileSync(reqPath, 'utf8');
420
- const lines = content.split('\n').filter(l => l.trim() && !l.startsWith('#'));
421
- result.pythonDeps = lines.length;
422
- } catch {
423
- // Ignore
424
- }
425
- }
426
-
427
- return result;
428
- }
429
-
430
- /**
431
- * Detect linting configuration in the project.
432
- * @param {string} targetDir - Project root.
433
- * @returns {string[]} Detected linters.
434
- */
435
- function detectLintConfig(targetDir) {
436
- const linters = [];
437
- const checks = [
438
- { files: ['eslint.config.js', 'eslint.config.mjs', '.eslintrc.js', '.eslintrc.json', '.eslintrc.yml', '.eslintrc'], tool: 'eslint' },
439
- { files: ['.prettierrc', '.prettierrc.json', '.prettierrc.js', 'prettier.config.js'], tool: 'prettier' },
440
- { files: ['.stylelintrc', '.stylelintrc.json', 'stylelint.config.js'], tool: 'stylelint' },
441
- { files: ['.pylintrc', 'pylintrc', 'setup.cfg'], tool: 'pylint' },
442
- { files: ['biome.json', 'biome.jsonc'], tool: 'biome' },
443
- { files: ['.oxlintrc.json'], tool: 'oxlint' },
444
- ];
445
-
446
- for (const { files, tool } of checks) {
447
- for (const file of files) {
448
- if (existsSync(join(targetDir, file))) {
449
- linters.push(tool);
450
- break;
451
- }
452
- }
453
- }
454
-
455
- return linters;
456
- }
457
-
458
- /**
459
- * Detect entry points in the project.
460
- * @param {string} targetDir - Project root.
461
- * @returns {string[]} Entry point file paths (relative).
462
- */
463
- function detectEntryPoints(targetDir) {
464
- const entries = [];
465
- const pkgPath = join(targetDir, 'package.json');
466
-
467
- if (existsSync(pkgPath)) {
468
- try {
469
- const pkg = JSON.parse(readFileSync(pkgPath, 'utf8'));
470
- if (pkg.main) entries.push(pkg.main);
471
- if (pkg.module) entries.push(pkg.module);
472
- if (pkg.bin) {
473
- if (typeof pkg.bin === 'string') {
474
- entries.push(pkg.bin);
475
- } else if (typeof pkg.bin === 'object') {
476
- entries.push(...Object.values(pkg.bin));
477
- }
478
- }
479
- if (pkg.exports) {
480
- const extractExportPaths = (exp) => {
481
- if (typeof exp === 'string') return [exp];
482
- if (typeof exp === 'object' && exp !== null) {
483
- return Object.values(exp).flatMap(v => extractExportPaths(v));
484
- }
485
- return [];
486
- };
487
- entries.push(...extractExportPaths(pkg.exports));
488
- }
489
- } catch {
490
- // Ignore
491
- }
492
- }
493
-
494
- // Common entry points
495
- const commonEntries = [
496
- 'index.js', 'index.ts', 'index.mjs',
497
- 'src/index.js', 'src/index.ts', 'src/main.js', 'src/main.ts',
498
- 'src/app.js', 'src/app.ts', 'app.js', 'app.ts',
499
- 'server.js', 'server.ts', 'src/server.js', 'src/server.ts',
500
- ];
501
-
502
- for (const entry of commonEntries) {
503
- if (!entries.includes(entry) && existsSync(join(targetDir, entry))) {
504
- entries.push(entry);
505
- }
506
- }
507
-
508
- return [...new Set(entries)];
509
- }
510
-
511
- /**
512
- * Detect test structure in the project.
513
- * @param {string} targetDir - Project root.
514
- * @returns {object} Test structure info.
515
- */
516
- function detectTestStructure(targetDir) {
517
- const testDirs = [];
518
- const possibleDirs = ['test', 'tests', '__tests__', 'spec', 'specs', 'e2e', 'cypress'];
519
-
520
- for (const dir of possibleDirs) {
521
- if (existsSync(join(targetDir, dir))) {
522
- testDirs.push(dir);
523
- }
524
- }
525
-
526
- // Check for colocated tests (tests alongside source files in src/)
527
- const srcDir = join(targetDir, 'src');
528
- let colocated = false;
529
- if (existsSync(srcDir)) {
530
- try {
531
- const srcFiles = readdirSync(srcDir, { recursive: true });
532
- colocated = srcFiles.some(f => typeof f === 'string' && /\.(test|spec)\.[jt]sx?$/.test(f));
533
- } catch {
534
- // Ignore
535
- }
536
- }
537
-
538
- return {
539
- testDirs,
540
- colocated,
541
- frameworks: detectTestFramework(targetDir),
542
- };
543
- }
544
-
545
- /**
546
- * Collect all file paths from a directory tree node.
547
- * @param {object} tree - Directory tree from scanDirectory.
548
- * @param {string} basePath - Accumulated path.
549
- * @returns {string[]} Flat list of file paths.
550
- */
551
- function collectFilePaths(tree, basePath = '') {
552
- const paths = [];
553
- const currentPath = basePath ? join(basePath, tree.name) : tree.name;
554
-
555
- if (tree.type === 'file') {
556
- paths.push(currentPath);
557
- } else if (tree.children) {
558
- for (const child of tree.children) {
559
- paths.push(...collectFilePaths(child, currentPath));
560
- }
561
- }
562
- return paths;
563
- }
564
-
565
- /**
566
- * Count file types from a flat list of file paths.
567
- * @param {string[]} files - Flat file path list.
568
- * @returns {object} Extension counts.
569
- */
570
- function countFileTypes(files) {
571
- const counts = {};
572
- for (const file of files) {
573
- const ext = extname(file) || '(no extension)';
574
- counts[ext] = (counts[ext] || 0) + 1;
575
- }
576
- // Sort by count descending
577
- return Object.fromEntries(
578
- Object.entries(counts).sort((a, b) => b[1] - a[1]),
579
- );
580
- }
581
-
582
- /**
583
- * Estimate total lines of code across all text files.
584
- * @param {string} targetDir - Project root.
585
- * @param {string[]} files - Relative file paths.
586
- * @returns {number} Estimated total lines.
587
- */
588
- function estimateTotalLines(targetDir, files) {
589
- const textExtensions = new Set([
590
- '.js', '.ts', '.jsx', '.tsx', '.mjs', '.mts',
591
- '.py', '.rb', '.go', '.rs', '.java', '.kt',
592
- '.c', '.cpp', '.h', '.hpp', '.cs',
593
- '.html', '.css', '.scss', '.less', '.sass',
594
- '.vue', '.svelte', '.astro',
595
- '.json', '.yaml', '.yml', '.toml', '.xml',
596
- '.md', '.txt', '.sh', '.bash',
597
- '.sql', '.graphql', '.gql',
598
- ]);
599
-
600
- let totalLines = 0;
601
- let sampledFiles = 0;
602
- const maxSample = 500;
603
-
604
- for (const file of files) {
605
- if (sampledFiles >= maxSample) break;
606
- const ext = extname(file);
607
- if (!textExtensions.has(ext)) continue;
608
-
609
- try {
610
- const content = readFileSync(join(targetDir, file), 'utf8');
611
- totalLines += content.split('\n').length;
612
- sampledFiles++;
613
- } catch {
614
- // Skip unreadable files
615
- }
616
- }
617
-
618
- // If we sampled, extrapolate
619
- const textFileCount = files.filter(f => textExtensions.has(extname(f))).length;
620
- if (sampledFiles > 0 && sampledFiles < textFileCount) {
621
- const avgLines = totalLines / sampledFiles;
622
- totalLines = Math.round(avgLines * textFileCount);
623
- }
624
-
625
- return totalLines;
626
- }
627
-
628
- // ---------------------------------------------------------------------------
629
- // Public API
630
- // ---------------------------------------------------------------------------
631
-
632
- /**
633
- * Map the target project's codebase structure.
634
- * @param {string} targetDir - Root directory of the project to scan.
635
- * @returns {object} CodebaseMap with directories, fileTypes, conventions, etc.
636
- */
637
- export function mapCodebase(targetDir) {
638
- if (!existsSync(targetDir)) {
639
- throw new Error(`Target directory does not exist: ${targetDir}`);
640
- }
641
-
642
- const stat = statSync(targetDir);
643
- if (!stat.isDirectory()) {
644
- throw new Error(`Target path is not a directory: ${targetDir}`);
645
- }
646
-
647
- // Scan directory tree
648
- const tree = scanDirectory(targetDir, 0, MAX_DEPTH);
649
-
650
- // Collect all file paths
651
- const allFiles = collectFilePaths(tree);
652
-
653
- // Build the map
654
- const map = {
655
- root: targetDir,
656
- scannedAt: new Date().toISOString(),
657
- directories: tree,
658
- fileTypes: countFileTypes(allFiles),
659
- conventions: detectConventions(allFiles),
660
- dependencies: parseDependencies(targetDir),
661
- entryPoints: detectEntryPoints(targetDir),
662
- testStructure: detectTestStructure(targetDir),
663
- lintConfig: detectLintConfig(targetDir),
664
- buildTools: detectBuildTools(targetDir),
665
- totalFiles: allFiles.length,
666
- totalLines: estimateTotalLines(targetDir, allFiles),
667
- };
668
-
669
- return map;
670
- }
671
-
672
- /**
673
- * Save a codebase map to .chati/codebase-map.json.
674
- * @param {string} targetDir - Project root directory.
675
- * @param {object} map - The CodebaseMap object.
676
- */
677
- export function saveCodebaseMap(targetDir, map) {
678
- const chatiDir = join(targetDir, '.chati');
679
- mkdirSync(chatiDir, { recursive: true });
680
- const mapPath = join(chatiDir, 'codebase-map.json');
681
- writeFileSync(mapPath, JSON.stringify(map, null, 2), 'utf8');
682
- return mapPath;
683
- }
684
-
685
- /**
686
- * Load a previously saved codebase map from .chati/codebase-map.json.
687
- * @param {string} targetDir - Project root directory.
688
- * @returns {object|null} The CodebaseMap or null if not found.
689
- */
690
- export function loadCodebaseMap(targetDir) {
691
- const mapPath = join(targetDir, '.chati', 'codebase-map.json');
692
- if (!existsSync(mapPath)) return null;
693
-
694
- try {
695
- return JSON.parse(readFileSync(mapPath, 'utf8'));
696
- } catch {
697
- return null;
698
- }
699
- }
700
-
701
- // ---------------------------------------------------------------------------
702
- // CLI entrypoint
703
- // ---------------------------------------------------------------------------
704
-
705
- const isMainModule = process.argv[1] && (
706
- process.argv[1].endsWith('codebase-mapper.js') ||
707
- process.argv[1].endsWith('codebase-mapper')
708
- );
709
-
710
- if (isMainModule) {
711
- const targetDir = process.argv[2] || process.cwd();
712
- console.log(`Mapping codebase: ${targetDir}`);
713
-
714
- try {
715
- const map = mapCodebase(targetDir);
716
- const savedPath = saveCodebaseMap(targetDir, map);
717
- console.log(`Total files: ${map.totalFiles}`);
718
- console.log(`Estimated lines: ${map.totalLines}`);
719
- console.log(`File types: ${Object.keys(map.fileTypes).length}`);
720
- console.log(`Build tools: ${map.buildTools.join(', ') || 'none detected'}`);
721
- console.log(`Test frameworks: ${map.testStructure.frameworks.join(', ') || 'none detected'}`);
722
- console.log(`Naming convention: ${map.conventions.dominant}`);
723
- console.log(`Map saved to: ${savedPath}`);
724
- } catch (err) {
725
- console.error(`Error: ${err.message}`);
726
- process.exit(1);
727
- }
728
- }