chati-dev 4.2.2 → 4.3.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +80 -53
- package/bin/chati.js +150 -5
- package/framework/agents/build/dev.md +509 -76
- package/framework/agents/deploy/devops.md +40 -25
- package/framework/agents/discover/brief.md +156 -22
- package/framework/agents/discover/brownfield-wu.md +24 -14
- package/framework/agents/discover/greenfield-wu.md +100 -14
- package/framework/agents/plan/architect-data-engineer.md +6 -6
- package/framework/agents/plan/architect-system.md +46 -12
- package/framework/agents/plan/architect.md +40 -20
- package/framework/agents/plan/detail.md +36 -24
- package/framework/agents/plan/phases.md +36 -26
- package/framework/agents/plan/tasks.md +114 -33
- package/framework/agents/plan/ux-brand-architect.md +240 -8
- package/framework/agents/plan/ux-component-engineer.md +28 -13
- package/framework/agents/plan/ux-researcher.md +7 -6
- package/framework/agents/plan/ux.md +55 -22
- package/framework/agents/quality/qa-implementation.md +143 -74
- package/framework/agents/quality/qa-planning.md +115 -42
- package/framework/agents/quality/qa-visual.md +439 -0
- package/framework/agents/shared/visualizer.md +128 -0
- package/framework/config.yaml +7 -6
- package/framework/constitution.md +127 -44
- package/framework/context/governance.md +12 -7
- package/framework/context/quality.md +6 -5
- package/framework/context/root.md +6 -6
- package/framework/data/entity-registry.yaml +377 -4
- package/framework/data/model-limits.json +19 -0
- package/framework/domains/agents/qa-visual.yaml +74 -0
- package/framework/domains/constitution.yaml +46 -2
- package/framework/domains/workflows/greenfield-fullstack.yaml +2 -2
- package/framework/hooks/advance-trigger.js +131 -0
- package/framework/hooks/brief-validator.js +83 -0
- package/framework/hooks/constitution-guard.js +24 -5
- package/framework/hooks/license-guard.js +62 -27
- package/framework/hooks/mode-governance.js +13 -2
- package/framework/hooks/model-governance.js +1 -0
- package/framework/hooks/post-dev.js +207 -0
- package/framework/hooks/prism-engine.js +274 -105
- package/framework/hooks/reasoning-escalator.js +371 -0
- package/framework/hooks/reference-trigger.js +117 -0
- package/framework/hooks/session-digest.js +50 -1
- package/framework/hooks/settings.json +32 -1
- package/framework/hooks/style-guard.js +25 -6
- package/framework/hooks/team-quality-gate.js +19 -12
- package/framework/hooks/undercover-guard.js +4 -2
- 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/context-engine.md +4 -5
- package/framework/intelligence/decision-engine.md +1 -1
- package/framework/intelligence/hooks-performance.md +3 -3
- package/framework/migrations/v1.0-to-v1.1.yaml +1 -1
- package/framework/migrations/v1.4-to-v2.0.yaml +11 -11
- package/framework/migrations/v4.0-to-v4.1.yaml +2 -2
- package/framework/migrations/v4.2-to-v4.3.yaml +29 -0
- package/framework/orchestrator/chati-router.js +387 -0
- package/framework/orchestrator/chati-update.md +40 -40
- package/framework/orchestrator/chati.md +294 -94
- package/framework/scaffold/motion-premium/README.md +89 -0
- package/framework/scaffold/motion-premium/app/globals.css.template +400 -0
- package/framework/scaffold/motion-premium/app/layout.tsx.template +110 -0
- package/framework/scaffold/motion-premium/components/animation/BackgroundCrossfadeProvider.tsx.template +170 -0
- package/framework/scaffold/motion-premium/components/animation/LenisProvider.tsx.template +49 -0
- package/framework/scaffold/motion-premium/components/animation/PageTransitionWrapper.tsx.template +83 -0
- package/framework/scaffold/motion-premium/components/animation/Preloader.tsx.template +171 -0
- package/framework/scaffold/motion-premium/components/ui/Container.tsx.template +69 -0
- package/framework/scaffold/motion-premium/components/ui/PageSection.tsx.template +74 -0
- package/framework/scaffold/motion-premium/lib/animations/gsap.ts.template +112 -0
- package/framework/scaffold/motion-premium/lib/animations/refreshCoordinator.ts.template +75 -0
- package/framework/scaffold/motion-premium/lib/animations/tokens.ts.template +119 -0
- package/framework/scaffold/motion-premium/lib/animations/useGsapContext.ts.template +92 -0
- package/framework/scaffold/motion-premium/lib/animations/useScrollSnapStepper.ts.template +265 -0
- package/framework/scaffold/motion-premium/lib/animations/useSmoothScroll.ts.template +67 -0
- package/framework/scaffold/motion-premium/lib/brand.ts.template +43 -0
- package/framework/scaffold/motion-premium/scaffold.yaml +174 -0
- package/framework/scaffold/motion-premium-3d/README.md +80 -0
- package/framework/scaffold/motion-premium-3d/app/(3d)/scroll-demo/ScrollDemoCanvas.tsx.template +81 -0
- package/framework/scaffold/motion-premium-3d/app/(3d)/scroll-demo/ScrollDemoClient.tsx.template +75 -0
- package/framework/scaffold/motion-premium-3d/app/(3d)/scroll-demo/page.tsx.template +26 -0
- package/framework/scaffold/motion-premium-3d/components/3d/CameraRig.tsx.template +100 -0
- package/framework/scaffold/motion-premium-3d/components/3d/CanvasProvider.tsx.template +85 -0
- package/framework/scaffold/motion-premium-3d/components/3d/InvalidateOnScroll.tsx.template +51 -0
- package/framework/scaffold/motion-premium-3d/components/3d/MeshCrossfade.tsx.template +79 -0
- package/framework/scaffold/motion-premium-3d/components/3d/ScrollCrossfade.tsx.template +88 -0
- package/framework/scaffold/motion-premium-3d/components/3d/ScrollScene.tsx.template +121 -0
- package/framework/scaffold/motion-premium-3d/components/webgl/SceneFallback.tsx.template +65 -0
- package/framework/scaffold/motion-premium-3d/components/webgl/WebGLContext.tsx.template +68 -0
- package/framework/scaffold/motion-premium-3d/lib/webgl/detect.ts.template +69 -0
- package/framework/scaffold/motion-premium-3d/scaffold.yaml +133 -0
- package/framework/schemas/session.schema.json +109 -21
- package/framework/scripts/reference-capture.js +430 -0
- package/framework/scripts/visual-qa.js +674 -0
- package/framework/tasks/orchestrator-handoff.md +1 -1
- package/framework/tasks/orchestrator-resume.md +1 -1
- package/framework/tasks/orchestrator-route.md +1 -1
- package/framework/tasks/orchestrator-status.md +3 -3
- package/framework/tasks/qa-planning-gate-define.md +1 -1
- package/framework/templates/brandbook-html-tmpl.md +1 -1
- package/framework/templates/brandbook-tmpl.yaml +1 -1
- package/framework/templates/component-spec-tmpl.yaml +1 -1
- package/framework/templates/design-token-tmpl.yaml +1 -1
- package/framework/templates/icon-system-tmpl.yaml +1 -1
- package/framework/templates/team-planning-tasks.yaml +6 -5
- package/framework/workflows/brownfield-discovery.yaml +2 -2
- package/framework/workflows/brownfield-fullstack.yaml +15 -11
- package/framework/workflows/brownfield-service.yaml +14 -10
- package/framework/workflows/brownfield-ui.yaml +15 -11
- package/framework/workflows/greenfield-fullstack.yaml +16 -13
- package/framework/workflows/quick-flow.yaml +3 -3
- package/framework/workflows/standard-flow.yaml +12 -9
- package/package.json +10 -5
- package/src/autonomy/autonomous-gate.js +1 -0
- package/src/autonomy/build-state.js +1 -2
- package/src/autonomy/progress-reporter.js +1 -1
- package/src/config/agent-customizer.js +11 -3
- package/src/config/claude-settings-generator.js +27 -7
- package/src/config/context-file-generator.js +41 -21
- package/src/config/framework-adapter.js +1 -0
- package/src/config/gemini-hooks-generator.js +19 -7
- package/src/config/mcp-configs.js +1 -0
- package/src/context/layers/l1-global.js +2 -1
- package/src/dashboard/data-reader.js +4 -3
- package/src/dashboard/layout.js +2 -1
- package/src/decision/analyzer.js +6 -30
- package/src/decision/engine.js +4 -28
- package/src/decision/registry-healer.js +3 -2
- package/src/decision/registry-updater.js +23 -14
- package/src/extensions/loader.js +2 -8
- package/src/gates/g1-planning-complete.js +2 -1
- package/src/gates/g2-qa-planning.js +2 -1
- package/src/gates/g3-implementation.js +2 -1
- package/src/gates/g4-qa-implementation.js +3 -2
- package/src/gates/g5-deploy-ready.js +2 -1
- package/src/health/engine.js +4 -3
- package/src/installer/core.js +422 -81
- package/src/installer/preflight.js +131 -0
- package/src/installer/provider-overlay.js +3 -3
- package/src/installer/scaffold-applier.js +358 -0
- package/src/installer/templates.js +46 -29
- package/src/installer/validator.js +17 -12
- package/src/intelligence/registry-manager.js +22 -29
- package/src/intelligence/timeline.js +11 -6
- package/src/license/commands.js +1 -1
- package/src/license/wait.js +102 -0
- package/src/memory/agent-memory.js +81 -0
- package/src/memory/dream.js +32 -1
- package/src/merger/replace-merger.js +28 -15
- package/src/orchestrator/agent-selector.js +2 -1
- package/src/orchestrator/cli.js +1869 -71
- package/src/orchestrator/doctor.js +270 -0
- package/src/orchestrator/handoff-engine.js +4 -3
- package/src/orchestrator/index.js +2 -0
- package/src/orchestrator/pipeline-manager.js +306 -15
- package/src/orchestrator/session-manager.js +331 -6
- package/src/tasks/handoff.js +3 -2
- package/src/telemetry/config.js +4 -3
- package/src/telemetry/schema.js +1 -0
- package/src/terminal/collector.js +3 -2
- package/src/terminal/index.js +1 -2
- package/src/terminal/isolation.js +52 -18
- package/src/terminal/prompt-builder.js +42 -25
- package/src/terminal/run-parallel.js +1 -1
- package/src/terminal/run-team.js +3 -3
- package/src/terminal/team-task-list.js +43 -4
- package/src/upgrade/backup.js +3 -2
- package/src/upgrade/checker.js +3 -2
- package/src/upgrade/migrator.js +65 -7
- package/src/upgrade/tracked-files-detector.js +86 -0
- package/src/upgrade/user-messages.js +94 -0
- package/src/utils/config-parser.js +2 -1
- package/src/utils/feature-flags.js +2 -1
- package/src/utils/flatten-entities.js +69 -0
- package/src/utils/framework-dir.js +16 -0
- package/src/utils/model-id.js +85 -0
- package/src/utils/provider-limits.js +84 -23
- package/src/utils/schema-validator.js +1 -1
- package/src/wizard/i18n.js +5 -4
- package/src/wizard/index.js +14 -0
- package/assets/logo - c/303/263pia.png +0 -0
- package/assets/logo.svg +0 -42
- package/assets/logo2.png +0 -0
- package/assets/social-preview.png +0 -0
- package/scripts/bundle-framework.js +0 -69
- package/scripts/changelog-generator.js +0 -222
- package/scripts/codebase-mapper.js +0 -728
- package/scripts/commit-message-generator.js +0 -167
- package/scripts/coverage-analyzer.js +0 -260
- package/scripts/dependency-analyzer.js +0 -280
- package/scripts/doctor/checks/agents.js +0 -77
- package/scripts/doctor/checks/constitution.js +0 -41
- package/scripts/doctor/checks/domain-alignment.js +0 -58
- package/scripts/doctor/checks/prism-layers.js +0 -84
- package/scripts/doctor/checks/registry.js +0 -55
- package/scripts/doctor/checks/schemas.js +0 -61
- package/scripts/doctor/fixes/reference-fix.js +0 -100
- package/scripts/doctor/fixes/registry-fix.js +0 -56
- package/scripts/doctor/index.js +0 -212
- package/scripts/framework-analyzer.js +0 -308
- package/scripts/generate-constitution-domain.js +0 -253
- package/scripts/generate-signing-key.js +0 -33
- package/scripts/health-check.js +0 -481
- package/scripts/ide-sync.js +0 -326
- package/scripts/performance-analyzer.js +0 -325
- package/scripts/plan-tracker.js +0 -278
- package/scripts/populate-entity-registry.js +0 -481
- package/scripts/pr-review.js +0 -317
- package/scripts/rollback-manager.js +0 -310
- package/scripts/semantic-lint.js +0 -328
- package/scripts/sign-manifest.js +0 -53
- package/scripts/stuck-detector.js +0 -343
- package/scripts/test-quality-assessment.js +0 -257
- package/scripts/validate-agents.js +0 -368
- package/scripts/validate-package.js +0 -505
- package/scripts/validate-tasks.js +0 -465
- package/src/autonomy/worktree-manager.js +0 -250
- package/src/intelligence/decision-engine.js +0 -256
- package/src/intelligence/document-sharder.js +0 -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
|
-
}
|