ft-scout 1.0.6 → 1.0.8
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/.ft/context.json +11 -8
- package/bin/src/commands/brief.d.ts.map +1 -1
- package/bin/src/commands/brief.js +12 -11
- package/bin/src/commands/brief.js.map +1 -1
- package/bin/src/commands/init.d.ts.map +1 -1
- package/bin/src/commands/init.js +222 -8
- package/bin/src/commands/init.js.map +1 -1
- package/bin/src/commands/owners.d.ts.map +1 -1
- package/bin/src/commands/owners.js +44 -1
- package/bin/src/commands/owners.js.map +1 -1
- package/bin/src/commands/risky.d.ts.map +1 -1
- package/bin/src/commands/risky.js +105 -16
- package/bin/src/commands/risky.js.map +1 -1
- package/bin/src/index.js +3 -2
- package/bin/src/index.js.map +1 -1
- package/bin/src/utils/config.d.ts.map +1 -1
- package/bin/src/utils/config.js.map +1 -1
- package/email-template.html +223 -0
- package/email-template.txt +45 -0
- package/package.json +1 -1
- package/scripts/send-brevo-campaign.cjs +167 -0
- package/scripts/send-email.js +77 -0
package/.ft/context.json
CHANGED
|
@@ -1,15 +1,18 @@
|
|
|
1
1
|
{
|
|
2
|
-
"projectName": "
|
|
3
|
-
"
|
|
4
|
-
"
|
|
2
|
+
"projectName": "FrontTerrainApp",
|
|
3
|
+
"repoUrl": "https://github.com/dark2quasar/Front-Terrain/FrontTerrainApp.git",
|
|
4
|
+
"targetDir": "C:\\Users\\rishi\\AppData\\Local\\Temp\\ft-scout-repos\\FrontTerrainApp-1785465176613",
|
|
5
|
+
"stack": "The FrontTerrainApp project is a social media application built with Flutter and React, utilizing Firebase for hosting and backend services. Its core stack includes TypeScript, React, and Flutter, with a mix of JavaScript and Python scripts for specific tasks.",
|
|
6
|
+
"entryPoint": "lib/main.dart",
|
|
5
7
|
"painPoints": [
|
|
6
|
-
"
|
|
7
|
-
"
|
|
8
|
+
"Complex folder structure with multiple build configurations",
|
|
9
|
+
"Usage of both Flutter and React, potentially leading to inconsistencies in code style and architecture"
|
|
8
10
|
],
|
|
9
11
|
"readOrder": [
|
|
12
|
+
"lib/main.dart",
|
|
10
13
|
"package.json",
|
|
11
|
-
"
|
|
12
|
-
"
|
|
14
|
+
"pubspec.yaml",
|
|
15
|
+
"firebase.json"
|
|
13
16
|
],
|
|
14
|
-
"createdAt": "2026-07-
|
|
17
|
+
"createdAt": "2026-07-31T02:33:02.673Z"
|
|
15
18
|
}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"brief.d.ts","sourceRoot":"","sources":["brief.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"brief.d.ts","sourceRoot":"","sources":["brief.ts"],"names":[],"mappings":"AAIA,wBAAsB,WAAW,kBA+BhC"}
|
|
@@ -1,27 +1,28 @@
|
|
|
1
1
|
import { spinner, note } from '@clack/prompts';
|
|
2
2
|
import chalk from 'chalk';
|
|
3
|
-
import
|
|
4
|
-
import path from 'path';
|
|
3
|
+
import { loadContext } from '../utils/config.js';
|
|
5
4
|
export async function handleBrief() {
|
|
6
5
|
const s = spinner();
|
|
7
6
|
s.start('Reading codebase structure and .ft metadata...');
|
|
8
|
-
const
|
|
9
|
-
if (!
|
|
7
|
+
const context = loadContext();
|
|
8
|
+
if (!context) {
|
|
10
9
|
s.stop(chalk.red('No local context found.'));
|
|
11
|
-
console.log('\nPlease run `ft init` first to index
|
|
10
|
+
console.log('\nPlease run `ft init` first to index a repository.');
|
|
12
11
|
return;
|
|
13
12
|
}
|
|
14
|
-
const rawData = fs.readFileSync(configPath, 'utf-8');
|
|
15
|
-
const context = JSON.parse(rawData);
|
|
16
13
|
s.stop('Repository analysis complete.');
|
|
17
|
-
|
|
14
|
+
const repoSource = context.repoUrl
|
|
15
|
+
? chalk.cyan(context.repoUrl)
|
|
16
|
+
: chalk.dim(`Root Repository (${context.targetDir || process.cwd()})`);
|
|
17
|
+
note(`Project: ${chalk.bold.green(context.projectName)}
|
|
18
|
+
Source: ${repoSource}
|
|
18
19
|
Primary Stack: ${chalk.yellow(context.stack)}
|
|
19
20
|
Entry Point: ${chalk.cyan(context.entryPoint)}
|
|
20
21
|
|
|
21
|
-
${chalk.bold('Known Pain Points:')}
|
|
22
|
-
${context.painPoints.map((p) =>
|
|
22
|
+
${chalk.bold('Known Architectural Risk / Pain Points:')}
|
|
23
|
+
${context.painPoints.map((p) => ` • ${p}`).join('\n')}
|
|
23
24
|
|
|
24
25
|
${chalk.bold('Suggested Reading Order:')}
|
|
25
|
-
${context.readOrder.map((r, idx) =>
|
|
26
|
+
${context.readOrder.map((r, idx) => ` ${idx + 1}. ${r}`).join('\n')}`, '💡 Onboarding Brief');
|
|
26
27
|
}
|
|
27
28
|
//# sourceMappingURL=brief.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"brief.js","sourceRoot":"","sources":["brief.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,OAAO,EAAE,IAAI,EAAE,MAAM,gBAAgB,CAAC;AAC/C,OAAO,KAAK,MAAM,OAAO,CAAC;AAC1B,OAAO,EAAE,
|
|
1
|
+
{"version":3,"file":"brief.js","sourceRoot":"","sources":["brief.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,OAAO,EAAE,IAAI,EAAE,MAAM,gBAAgB,CAAC;AAC/C,OAAO,KAAK,MAAM,OAAO,CAAC;AAC1B,OAAO,EAAE,WAAW,EAAE,MAAM,oBAAoB,CAAC;AAEjD,MAAM,CAAC,KAAK,UAAU,WAAW;IAC/B,MAAM,CAAC,GAAG,OAAO,EAAE,CAAC;IACpB,CAAC,CAAC,KAAK,CAAC,gDAAgD,CAAC,CAAC;IAE1D,MAAM,OAAO,GAAG,WAAW,EAAE,CAAC;IAE9B,IAAI,CAAC,OAAO,EAAE,CAAC;QACb,CAAC,CAAC,IAAI,CAAC,KAAK,CAAC,GAAG,CAAC,yBAAyB,CAAC,CAAC,CAAC;QAC7C,OAAO,CAAC,GAAG,CAAC,qDAAqD,CAAC,CAAC;QACnE,OAAO;IACT,CAAC;IAED,CAAC,CAAC,IAAI,CAAC,+BAA+B,CAAC,CAAC;IAExC,MAAM,UAAU,GAAG,OAAO,CAAC,OAAO;QAChC,CAAC,CAAC,KAAK,CAAC,IAAI,CAAC,OAAO,CAAC,OAAO,CAAC;QAC7B,CAAC,CAAC,KAAK,CAAC,GAAG,CAAC,oBAAoB,OAAO,CAAC,SAAS,IAAI,OAAO,CAAC,GAAG,EAAE,GAAG,CAAC,CAAC;IAEzE,IAAI,CACF,YAAY,KAAK,CAAC,IAAI,CAAC,KAAK,CAAC,OAAO,CAAC,WAAW,CAAC;UAC3C,UAAU;iBACH,KAAK,CAAC,MAAM,CAAC,OAAO,CAAC,KAAK,CAAC;eAC7B,KAAK,CAAC,IAAI,CAAC,OAAO,CAAC,UAAU,CAAC;;EAE3C,KAAK,CAAC,IAAI,CAAC,yCAAyC,CAAC;EACrD,OAAO,CAAC,UAAU,CAAC,GAAG,CAAC,CAAC,CAAS,EAAE,EAAE,CAAC,MAAM,CAAC,EAAE,CAAC,CAAC,IAAI,CAAC,IAAI,CAAC;;EAE3D,KAAK,CAAC,IAAI,CAAC,0BAA0B,CAAC;EACtC,OAAO,CAAC,SAAS,CAAC,GAAG,CAAC,CAAC,CAAS,EAAE,GAAW,EAAE,EAAE,CAAC,IAAI,GAAG,GAAG,CAAC,KAAK,CAAC,EAAE,CAAC,CAAC,IAAI,CAAC,IAAI,CAAC,EAAE,EACjF,qBAAqB,CACtB,CAAC;AACJ,CAAC"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"init.d.ts","sourceRoot":"","sources":["init.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"init.d.ts","sourceRoot":"","sources":["init.ts"],"names":[],"mappings":"AAsFA,wBAAsB,UAAU,CAAC,OAAO,CAAC,EAAE,MAAM,iBA8LhD"}
|
package/bin/src/commands/init.js
CHANGED
|
@@ -1,22 +1,236 @@
|
|
|
1
1
|
import fs from 'fs';
|
|
2
2
|
import path from 'path';
|
|
3
|
+
import os from 'os';
|
|
4
|
+
import { spinner, note, password, isCancel } from '@clack/prompts';
|
|
5
|
+
import chalk from 'chalk';
|
|
6
|
+
import { simpleGit } from 'simple-git';
|
|
3
7
|
import { generateRepoSummary } from '../engine/llm.js';
|
|
4
8
|
import { saveContext } from '../utils/config.js';
|
|
9
|
+
function extractNpmPackageName(target) {
|
|
10
|
+
if (target.startsWith('npm:')) {
|
|
11
|
+
return target.slice(4).trim();
|
|
12
|
+
}
|
|
13
|
+
const npmUrlMatch = target.match(/^https?:\/\/(?:www\.)?npmjs\.com\/package\/(@?[^/]+(?:\/[^/]+)?)/i);
|
|
14
|
+
if (npmUrlMatch && npmUrlMatch[1]) {
|
|
15
|
+
return npmUrlMatch[1];
|
|
16
|
+
}
|
|
17
|
+
const isGitOrFile = /^https?:\/\//i.test(target) ||
|
|
18
|
+
/^git@/i.test(target) ||
|
|
19
|
+
target.includes('github.com') ||
|
|
20
|
+
target.includes('gitlab.com') ||
|
|
21
|
+
target.endsWith('.git');
|
|
22
|
+
if (!isGitOrFile) {
|
|
23
|
+
if (/^(@[a-z0-9-~][a-z0-9-._~]*\/)?[a-z0-9-~][a-z0-9-._~]*$/i.test(target)) {
|
|
24
|
+
return target;
|
|
25
|
+
}
|
|
26
|
+
}
|
|
27
|
+
return null;
|
|
28
|
+
}
|
|
29
|
+
async function getNpmRepoUrl(pkgName) {
|
|
30
|
+
try {
|
|
31
|
+
const res = await fetch(`https://registry.npmjs.org/${encodeURIComponent(pkgName)}`);
|
|
32
|
+
if (!res.ok) {
|
|
33
|
+
return null;
|
|
34
|
+
}
|
|
35
|
+
const data = (await res.json());
|
|
36
|
+
const repo = data.repository;
|
|
37
|
+
if (!repo)
|
|
38
|
+
return null;
|
|
39
|
+
let url = typeof repo === 'string' ? repo : repo.url;
|
|
40
|
+
if (!url)
|
|
41
|
+
return null;
|
|
42
|
+
url = url.replace(/^git\+/, '');
|
|
43
|
+
url = url.replace(/^git:\/\//, 'https://');
|
|
44
|
+
url = url.replace(/^ssh:\/\/git@github\.com\//, 'https://github.com/');
|
|
45
|
+
url = url.replace(/^git@github\.com:/, 'https://github.com/');
|
|
46
|
+
if (url.startsWith('github:')) {
|
|
47
|
+
url = `https://github.com/${url.replace('github:', '')}.git`;
|
|
48
|
+
}
|
|
49
|
+
if (url.startsWith('gitlab:')) {
|
|
50
|
+
url = `https://gitlab.com/${url.replace('gitlab:', '')}.git`;
|
|
51
|
+
}
|
|
52
|
+
if (url.startsWith('bitbucket:')) {
|
|
53
|
+
url = `https://bitbucket.org/${url.replace('bitbucket:', '')}.git`;
|
|
54
|
+
}
|
|
55
|
+
return url;
|
|
56
|
+
}
|
|
57
|
+
catch {
|
|
58
|
+
return null;
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
function cleanGitHubUrl(targetUrl) {
|
|
62
|
+
if (targetUrl.includes('github.com')) {
|
|
63
|
+
const match = targetUrl.match(/github\.com\/([^/]+(?:\/[^/]+)+)/i);
|
|
64
|
+
if (match && match[1]) {
|
|
65
|
+
let parts = match[1].split('/').filter(Boolean);
|
|
66
|
+
const branchIndex = parts.findIndex((p) => ['tree', 'blob', 'src'].includes(p.toLowerCase()));
|
|
67
|
+
if (branchIndex !== -1) {
|
|
68
|
+
parts = parts.slice(0, branchIndex);
|
|
69
|
+
}
|
|
70
|
+
const cleanParts = parts.map((p, i) => (i === parts.length - 1 ? p.replace(/\.git$/i, '') : p));
|
|
71
|
+
if (cleanParts.length >= 2) {
|
|
72
|
+
const owner = cleanParts[cleanParts.length - 2];
|
|
73
|
+
const repo = cleanParts[cleanParts.length - 1];
|
|
74
|
+
return `https://github.com/${owner}/${repo}.git`;
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
return targetUrl;
|
|
79
|
+
}
|
|
5
80
|
export async function handleInit(repoUrl) {
|
|
6
|
-
const
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
81
|
+
const s = spinner();
|
|
82
|
+
let targetDir = process.cwd();
|
|
83
|
+
if (repoUrl && repoUrl.trim()) {
|
|
84
|
+
const rawTarget = repoUrl.trim();
|
|
85
|
+
const resolvedPath = path.resolve(rawTarget);
|
|
86
|
+
if (fs.existsSync(resolvedPath) && fs.statSync(resolvedPath).isDirectory()) {
|
|
87
|
+
targetDir = resolvedPath;
|
|
88
|
+
}
|
|
89
|
+
else {
|
|
90
|
+
let gitTargetUrl = cleanGitHubUrl(rawTarget);
|
|
91
|
+
const npmPkgName = extractNpmPackageName(rawTarget);
|
|
92
|
+
const isGitUrl = (/^https?:\/\//i.test(rawTarget) && !rawTarget.includes('npmjs.com')) ||
|
|
93
|
+
/^git@/i.test(rawTarget) ||
|
|
94
|
+
rawTarget.includes('github.com') ||
|
|
95
|
+
rawTarget.includes('gitlab.com') ||
|
|
96
|
+
rawTarget.endsWith('.git');
|
|
97
|
+
if (npmPkgName && !isGitUrl) {
|
|
98
|
+
s.start(`Resolving npm package repository (${chalk.cyan(npmPkgName)})...`);
|
|
99
|
+
const resolvedUrl = await getNpmRepoUrl(npmPkgName);
|
|
100
|
+
if (!resolvedUrl) {
|
|
101
|
+
s.stop(chalk.red(`Failed to resolve npm package repository.`));
|
|
102
|
+
note(chalk.red(`The specified repository is private or does not exist.`), '❌ Invalid Target');
|
|
103
|
+
return;
|
|
104
|
+
}
|
|
105
|
+
gitTargetUrl = cleanGitHubUrl(resolvedUrl);
|
|
106
|
+
s.stop(`Resolved npm package to repository ${chalk.cyan(gitTargetUrl)}.`);
|
|
107
|
+
}
|
|
108
|
+
s.start(`Cloning remote repository (${chalk.cyan(gitTargetUrl)})...`);
|
|
109
|
+
const repoName = path.basename(gitTargetUrl, '.git') || 'cloned-repo';
|
|
110
|
+
const tempParent = path.join(os.tmpdir(), 'ft-scout-repos');
|
|
111
|
+
targetDir = path.join(tempParent, `${repoName}-${Date.now()}`);
|
|
112
|
+
if (!fs.existsSync(tempParent)) {
|
|
113
|
+
fs.mkdirSync(tempParent, { recursive: true });
|
|
114
|
+
}
|
|
115
|
+
const git = simpleGit();
|
|
116
|
+
try {
|
|
117
|
+
await git.clone(gitTargetUrl, targetDir, ['--depth', '1']);
|
|
118
|
+
s.stop(`Remote repository cloned successfully.`);
|
|
119
|
+
}
|
|
120
|
+
catch (err) {
|
|
121
|
+
s.stop(chalk.yellow(`Initial clone failed (repository may be private or require authentication).`));
|
|
122
|
+
// Interactive login / token prompt for private repos
|
|
123
|
+
if (gitTargetUrl.startsWith('https://')) {
|
|
124
|
+
const tokenInput = await password({
|
|
125
|
+
message: 'Enter your GitHub / GitLab Personal Access Token (PAT) to access this private repository:',
|
|
126
|
+
mask: '*',
|
|
127
|
+
validate: (val) => (!val || !val.trim() ? 'Token cannot be empty' : undefined),
|
|
128
|
+
});
|
|
129
|
+
if (!isCancel(tokenInput) && typeof tokenInput === 'string' && tokenInput.trim()) {
|
|
130
|
+
const cleanToken = tokenInput.trim();
|
|
131
|
+
// Inject token into URL: https://<token>@github.com/...
|
|
132
|
+
const authenticatedUrl = gitTargetUrl.replace(/^https:\/\//i, `https://${cleanToken}@`);
|
|
133
|
+
const retrySpinner = spinner();
|
|
134
|
+
retrySpinner.start(`Retrying clone with provided token...`);
|
|
135
|
+
try {
|
|
136
|
+
if (fs.existsSync(targetDir)) {
|
|
137
|
+
fs.rmSync(targetDir, { recursive: true, force: true });
|
|
138
|
+
}
|
|
139
|
+
await git.clone(authenticatedUrl, targetDir, ['--depth', '1']);
|
|
140
|
+
retrySpinner.stop(chalk.green(`Remote repository cloned successfully!`));
|
|
141
|
+
}
|
|
142
|
+
catch (retryErr) {
|
|
143
|
+
retrySpinner.stop(chalk.red(`Authentication failed with the provided token.`));
|
|
144
|
+
note(chalk.red(`Could not clone repository with the provided token.\n` +
|
|
145
|
+
`Please check that your token has repo read permissions.\n\n` +
|
|
146
|
+
`Alternative options:\n` +
|
|
147
|
+
` • Use SSH: ${chalk.cyan('ft init git@github.com:owner/repo.git')}\n` +
|
|
148
|
+
` • Or clone locally first: ${chalk.cyan('ft init ./my-repo-folder')}`), '❌ Authentication Failed');
|
|
149
|
+
return;
|
|
150
|
+
}
|
|
151
|
+
}
|
|
152
|
+
else {
|
|
153
|
+
note(chalk.yellow('Login cancelled. Repository initialization aborted.'), '❌ Clone Cancelled');
|
|
154
|
+
return;
|
|
155
|
+
}
|
|
156
|
+
}
|
|
157
|
+
else {
|
|
158
|
+
note(chalk.red(`The specified repository is private or does not exist.\n\n` +
|
|
159
|
+
chalk.bold('🔒 How to access private repositories:') + '\n' +
|
|
160
|
+
` • Use SSH URL: ${chalk.cyan('ft init git@github.com:owner/repo.git')}\n` +
|
|
161
|
+
` • Or use PAT Token URL: ${chalk.cyan('ft init https://<TOKEN>@github.com/owner/repo.git')}\n` +
|
|
162
|
+
` • Or clone locally first and run: ${chalk.cyan('ft-scout init ./my-repo-folder')}`), '❌ Clone Error');
|
|
163
|
+
return;
|
|
164
|
+
}
|
|
165
|
+
}
|
|
166
|
+
}
|
|
167
|
+
}
|
|
168
|
+
s.start(`Analyzing repository at ${chalk.cyan(targetDir)}...`);
|
|
169
|
+
// Scan file tree up to 2 levels deep
|
|
170
|
+
const getFiles = (dir, depth = 2) => {
|
|
171
|
+
let results = [];
|
|
172
|
+
try {
|
|
173
|
+
const list = fs.readdirSync(dir);
|
|
174
|
+
for (const file of list) {
|
|
175
|
+
if (['node_modules', '.git', 'dist', 'build', '.dart_tool', '.next', 'out', 'vendor'].includes(file))
|
|
176
|
+
continue;
|
|
177
|
+
const fullPath = path.join(dir, file);
|
|
178
|
+
const relPath = path.relative(dir, fullPath);
|
|
179
|
+
const stat = fs.statSync(fullPath);
|
|
180
|
+
if (stat.isDirectory()) {
|
|
181
|
+
results.push(relPath + '/');
|
|
182
|
+
if (depth > 1) {
|
|
183
|
+
results = results.concat(getFiles(fullPath, depth - 1));
|
|
184
|
+
}
|
|
185
|
+
}
|
|
186
|
+
else {
|
|
187
|
+
results.push(relPath);
|
|
188
|
+
}
|
|
189
|
+
}
|
|
190
|
+
}
|
|
191
|
+
catch { }
|
|
192
|
+
return results;
|
|
193
|
+
};
|
|
194
|
+
const filesList = getFiles(targetDir, 2);
|
|
195
|
+
// Collect configuration & manifest files (package.json, pubspec.yaml, Cargo.toml, pyproject.toml, go.mod, etc.)
|
|
196
|
+
const manifestNames = ['package.json', 'pubspec.yaml', 'pyproject.toml', 'Cargo.toml', 'go.mod', 'requirements.txt', 'pom.xml', 'build.gradle'];
|
|
197
|
+
let manifestContent = '';
|
|
198
|
+
for (const name of manifestNames) {
|
|
199
|
+
const p = path.join(targetDir, name);
|
|
200
|
+
if (fs.existsSync(p)) {
|
|
201
|
+
manifestContent += `--- ${name} ---\n${fs.readFileSync(p, 'utf-8').slice(0, 1500)}\n\n`;
|
|
202
|
+
}
|
|
203
|
+
}
|
|
204
|
+
let projectName = path.basename(targetDir);
|
|
205
|
+
if (repoUrl && repoUrl.trim()) {
|
|
206
|
+
const rawTarget = repoUrl.trim();
|
|
207
|
+
if (/^https?:\/\//i.test(rawTarget) || /^git@/i.test(rawTarget) || rawTarget.includes('github.com') || rawTarget.includes('gitlab.com')) {
|
|
208
|
+
projectName = path.basename(rawTarget, '.git');
|
|
209
|
+
}
|
|
210
|
+
}
|
|
211
|
+
const aiInsight = await generateRepoSummary(projectName, filesList, manifestContent || '{}');
|
|
212
|
+
// Save context into local .ft folder
|
|
13
213
|
saveContext({
|
|
14
|
-
projectName
|
|
214
|
+
projectName,
|
|
215
|
+
repoUrl: repoUrl?.trim() || undefined,
|
|
216
|
+
targetDir,
|
|
15
217
|
stack: aiInsight.summary,
|
|
16
218
|
entryPoint: aiInsight.entryPoint,
|
|
17
219
|
painPoints: aiInsight.painPoints,
|
|
18
220
|
readOrder: aiInsight.readOrder,
|
|
19
221
|
createdAt: new Date().toISOString(),
|
|
20
222
|
});
|
|
223
|
+
s.stop('Repository analysis complete.');
|
|
224
|
+
// Print repository summary
|
|
225
|
+
note(`Project Name: ${chalk.bold.green(projectName)}
|
|
226
|
+
Location: ${chalk.dim(targetDir)}
|
|
227
|
+
Primary Stack: ${chalk.yellow(aiInsight.summary)}
|
|
228
|
+
Entry Point: ${chalk.cyan(aiInsight.entryPoint)}
|
|
229
|
+
|
|
230
|
+
${chalk.bold('Known Architectural Risk / Pain Points:')}
|
|
231
|
+
${aiInsight.painPoints.map((p) => ` • ${p}`).join('\n')}
|
|
232
|
+
|
|
233
|
+
${chalk.bold('Suggested Reading Order:')}
|
|
234
|
+
${aiInsight.readOrder.map((r, idx) => ` ${idx + 1}. ${r}`).join('\n')}`, '💡 Onboarding Brief');
|
|
21
235
|
}
|
|
22
236
|
//# sourceMappingURL=init.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"init.js","sourceRoot":"","sources":["init.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,MAAM,IAAI,CAAC;AACpB,OAAO,IAAI,MAAM,MAAM,CAAC;AACxB,OAAO,EAAE,mBAAmB,EAAE,MAAM,kBAAkB,CAAC;AACvD,OAAO,EAAE,WAAW,EAAE,MAAM,oBAAoB,CAAC;AAEjD,MAAM,CAAC,KAAK,
|
|
1
|
+
{"version":3,"file":"init.js","sourceRoot":"","sources":["init.ts"],"names":[],"mappings":"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"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"owners.d.ts","sourceRoot":"","sources":["owners.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"owners.d.ts","sourceRoot":"","sources":["owners.ts"],"names":[],"mappings":"AAIA,wBAAsB,YAAY,CAAC,UAAU,EAAE,MAAM,iBAkEpD"}
|
|
@@ -1,2 +1,45 @@
|
|
|
1
|
-
|
|
1
|
+
import { spinner, note } from '@clack/prompts';
|
|
2
|
+
import chalk from 'chalk';
|
|
3
|
+
import { getFileOwners } from '../engine/git.js';
|
|
4
|
+
export async function handleOwners(targetPath) {
|
|
5
|
+
const s = spinner();
|
|
6
|
+
s.start(`Analyzing git history for ${chalk.cyan(targetPath)}...`);
|
|
7
|
+
if (!targetPath || !targetPath.trim()) {
|
|
8
|
+
s.stop(chalk.red('No path provided.'));
|
|
9
|
+
note(chalk.red('Please specify a file or directory path, e.g. `ft owners src/index.ts`'), '👥 File Knowledge Holders');
|
|
10
|
+
return;
|
|
11
|
+
}
|
|
12
|
+
if (/^https?:\/\//i.test(targetPath) ||
|
|
13
|
+
/^git@/i.test(targetPath) ||
|
|
14
|
+
targetPath.includes('github.com') ||
|
|
15
|
+
targetPath.includes('gitlab.com')) {
|
|
16
|
+
s.stop(chalk.yellow('Remote repository URL detected.'));
|
|
17
|
+
note(`\`ft owners\` is designed for local files and directories (e.g. ${chalk.cyan('ft owners src/index.ts')} or ${chalk.cyan('ft owners .')}).\n\nTo onboard and index a remote repository, please run:\n ${chalk.cyan(`ft init ${targetPath}`)}`, '💡 Usage Tip');
|
|
18
|
+
return;
|
|
19
|
+
}
|
|
20
|
+
try {
|
|
21
|
+
const owners = await getFileOwners(targetPath);
|
|
22
|
+
if (owners.length === 0) {
|
|
23
|
+
s.stop('Ownership analysis complete.');
|
|
24
|
+
note(chalk.yellow(`No commit history found for "${targetPath}". Make sure the file exists and is tracked by Git.`), '👥 File Knowledge Holders');
|
|
25
|
+
return;
|
|
26
|
+
}
|
|
27
|
+
s.stop('Ownership analysis complete.');
|
|
28
|
+
const formattedList = owners
|
|
29
|
+
.map((o, idx) => {
|
|
30
|
+
const rank = chalk.bold(`#${idx + 1}`);
|
|
31
|
+
const pct = chalk.green(`${o.percentage}%`);
|
|
32
|
+
const dateStr = o.lastTouched
|
|
33
|
+
? chalk.dim(`(last active: ${new Date(o.lastTouched).toLocaleDateString()})`)
|
|
34
|
+
: '';
|
|
35
|
+
return ` ${rank} ${chalk.bold(o.author)} — ${pct} (${o.commits} commit${o.commits > 1 ? 's' : ''}) ${dateStr}`;
|
|
36
|
+
})
|
|
37
|
+
.join('\n');
|
|
38
|
+
note(`Target: ${chalk.bold.cyan(targetPath)}\n\n${chalk.bold('Knowledge Holders:')}\n${formattedList}`, '👥 File Knowledge Holders');
|
|
39
|
+
}
|
|
40
|
+
catch (err) {
|
|
41
|
+
s.stop(chalk.red('Ownership analysis failed.'));
|
|
42
|
+
note(chalk.red(`Could not fetch git history for "${targetPath}". Make sure this is a valid git repository.\nError: ${err?.message || err}`), '👥 File Knowledge Holders');
|
|
43
|
+
}
|
|
44
|
+
}
|
|
2
45
|
//# sourceMappingURL=owners.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"owners.js","sourceRoot":"","sources":["owners.ts"],"names":[],"mappings":"AAAA,MAAM,CAAC,KAAK,UAAU,YAAY,CAAC,
|
|
1
|
+
{"version":3,"file":"owners.js","sourceRoot":"","sources":["owners.ts"],"names":[],"mappings":"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"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"risky.d.ts","sourceRoot":"","sources":["risky.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"risky.d.ts","sourceRoot":"","sources":["risky.ts"],"names":[],"mappings":"AA0DA,wBAAsB,WAAW,kBA8FhC"}
|
|
@@ -1,28 +1,93 @@
|
|
|
1
1
|
import { spinner, note } from '@clack/prompts';
|
|
2
2
|
import chalk from 'chalk';
|
|
3
3
|
import { simpleGit } from 'simple-git';
|
|
4
|
+
async function getHotfixLineRanges(git, file, hotfixHashes) {
|
|
5
|
+
const lineCounts = {};
|
|
6
|
+
for (const hash of hotfixHashes.slice(0, 5)) {
|
|
7
|
+
try {
|
|
8
|
+
const patch = await git.show(['-p', hash, '--', file]);
|
|
9
|
+
const hunkHeaders = patch.match(/@@ -\d+(?:,\d+)? \+(\d+)(?:,(\d+))? @@/g);
|
|
10
|
+
if (hunkHeaders) {
|
|
11
|
+
for (const header of hunkHeaders) {
|
|
12
|
+
const match = /@@ -\d+(?:,\d+)? \+(\d+)(?:,(\d+))? @@/.exec(header);
|
|
13
|
+
if (match && match[1]) {
|
|
14
|
+
const startLine = parseInt(match[1], 10);
|
|
15
|
+
const count = match[2] ? parseInt(match[2], 10) : 1;
|
|
16
|
+
for (let l = startLine; l < startLine + Math.max(count, 1); l++) {
|
|
17
|
+
lineCounts[l] = (lineCounts[l] || 0) + 1;
|
|
18
|
+
}
|
|
19
|
+
}
|
|
20
|
+
}
|
|
21
|
+
}
|
|
22
|
+
}
|
|
23
|
+
catch {
|
|
24
|
+
// Ignore diff errors
|
|
25
|
+
}
|
|
26
|
+
}
|
|
27
|
+
const lines = Object.keys(lineCounts).map(Number).sort((a, b) => a - b);
|
|
28
|
+
if (lines.length === 0 || lines[0] === undefined)
|
|
29
|
+
return [];
|
|
30
|
+
const ranges = [];
|
|
31
|
+
let start = lines[0];
|
|
32
|
+
let prev = lines[0];
|
|
33
|
+
for (let i = 1; i < lines.length; i++) {
|
|
34
|
+
const current = lines[i];
|
|
35
|
+
if (current !== undefined && current === prev + 1) {
|
|
36
|
+
prev = current;
|
|
37
|
+
}
|
|
38
|
+
else if (current !== undefined) {
|
|
39
|
+
ranges.push(start === prev ? `L${start}` : `L${start}-${prev}`);
|
|
40
|
+
start = current;
|
|
41
|
+
prev = current;
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
ranges.push(start === prev ? `L${start}` : `L${start}-${prev}`);
|
|
45
|
+
return ranges.slice(0, 4);
|
|
46
|
+
}
|
|
4
47
|
export async function handleRisky() {
|
|
5
48
|
const s = spinner();
|
|
6
49
|
s.start('Analyzing git history & churn patterns...');
|
|
7
50
|
const git = simpleGit();
|
|
8
|
-
|
|
51
|
+
let log;
|
|
52
|
+
try {
|
|
53
|
+
log = await git.log({ maxCount: 200 });
|
|
54
|
+
}
|
|
55
|
+
catch {
|
|
56
|
+
s.stop('Git log analysis failed.');
|
|
57
|
+
note(chalk.red('Could not fetch git history. Make sure this is a valid git repository with commits.'), '🔥 Codebase Risk Map');
|
|
58
|
+
return;
|
|
59
|
+
}
|
|
9
60
|
const fileStats = {};
|
|
10
61
|
for (const commit of log.all) {
|
|
62
|
+
if (!commit.hash)
|
|
63
|
+
continue;
|
|
11
64
|
const isHotfix = /fix|bug|hotfix|patch/i.test(commit.message);
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
65
|
+
try {
|
|
66
|
+
const diff = await git.show(['--name-only', '--oneline', commit.hash]);
|
|
67
|
+
const files = diff.split('\n').slice(1).filter(Boolean);
|
|
68
|
+
for (const file of files) {
|
|
69
|
+
if (!fileStats[file]) {
|
|
70
|
+
fileStats[file] = { total: 0, hotfixes: 0, messages: [], hashes: [] };
|
|
71
|
+
}
|
|
72
|
+
const stat = fileStats[file];
|
|
73
|
+
if (!stat)
|
|
74
|
+
continue;
|
|
75
|
+
stat.total += 1;
|
|
76
|
+
if (isHotfix) {
|
|
77
|
+
stat.hotfixes += 1;
|
|
78
|
+
stat.hashes.push(commit.hash);
|
|
79
|
+
const cleanMsg = commit.message.split('\n')[0]?.trim();
|
|
80
|
+
if (cleanMsg && !stat.messages.includes(cleanMsg) && stat.messages.length < 3) {
|
|
81
|
+
stat.messages.push(cleanMsg);
|
|
82
|
+
}
|
|
83
|
+
}
|
|
18
84
|
}
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
85
|
+
}
|
|
86
|
+
catch {
|
|
87
|
+
// Ignore single commit parsing errors
|
|
22
88
|
}
|
|
23
89
|
}
|
|
24
90
|
s.stop('Fragility analysis complete.');
|
|
25
|
-
// Sort by risk score
|
|
26
91
|
const results = Object.entries(fileStats)
|
|
27
92
|
.map(([file, stats]) => {
|
|
28
93
|
let riskLevel = 'LOW';
|
|
@@ -30,14 +95,38 @@ export async function handleRisky() {
|
|
|
30
95
|
riskLevel = 'HIGH';
|
|
31
96
|
else if (stats.hotfixes >= 1 || stats.total > 7)
|
|
32
97
|
riskLevel = 'MEDIUM';
|
|
33
|
-
return {
|
|
98
|
+
return {
|
|
99
|
+
file,
|
|
100
|
+
churnCount: stats.total,
|
|
101
|
+
hotfixes: stats.hotfixes,
|
|
102
|
+
riskLevel,
|
|
103
|
+
recentMessages: stats.messages,
|
|
104
|
+
hotfixHashes: stats.hashes,
|
|
105
|
+
};
|
|
34
106
|
})
|
|
35
107
|
.filter((r) => r.riskLevel !== 'LOW')
|
|
36
108
|
.sort((a, b) => b.hotfixes - a.hotfixes);
|
|
37
|
-
|
|
109
|
+
if (results.length === 0) {
|
|
110
|
+
note(chalk.green('No high-risk hotspots found!'), '🔥 Codebase Risk Map');
|
|
111
|
+
return;
|
|
112
|
+
}
|
|
113
|
+
const topResults = results.slice(0, 5);
|
|
114
|
+
const formattedBlocks = [];
|
|
115
|
+
for (const r of topResults) {
|
|
38
116
|
const icon = r.riskLevel === 'HIGH' ? chalk.red('🔴 HIGH RISK') : chalk.yellow('🟡 MEDIUM RISK');
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
117
|
+
let block = `${icon} ${chalk.bold(r.file)}\n └─ ${r.churnCount} commits, ${r.hotfixes} hotfixes detected`;
|
|
118
|
+
const lineRanges = await getHotfixLineRanges(git, r.file, r.hotfixHashes);
|
|
119
|
+
if (lineRanges.length > 0) {
|
|
120
|
+
block += `\n └─ Hotspot lines: ${chalk.cyan(lineRanges.join(', '))}`;
|
|
121
|
+
}
|
|
122
|
+
if (r.recentMessages.length > 0) {
|
|
123
|
+
block += `\n └─ Recent fixes:`;
|
|
124
|
+
r.recentMessages.forEach((msg) => {
|
|
125
|
+
block += `\n • ${chalk.dim(msg)}`;
|
|
126
|
+
});
|
|
127
|
+
}
|
|
128
|
+
formattedBlocks.push(block);
|
|
129
|
+
}
|
|
130
|
+
note(formattedBlocks.join('\n\n'), '🔥 Codebase Risk Map');
|
|
42
131
|
}
|
|
43
132
|
//# sourceMappingURL=risky.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"risky.js","sourceRoot":"","sources":["risky.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,OAAO,EAAE,IAAI,EAAE,MAAM,gBAAgB,CAAC;AAC/C,OAAO,KAAK,MAAM,OAAO,CAAC;AAC1B,OAAO,EAAE,SAAS,EAAkB,MAAM,YAAY,CAAC;
|
|
1
|
+
{"version":3,"file":"risky.js","sourceRoot":"","sources":["risky.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,OAAO,EAAE,IAAI,EAAE,MAAM,gBAAgB,CAAC;AAC/C,OAAO,KAAK,MAAM,OAAO,CAAC;AAC1B,OAAO,EAAE,SAAS,EAAkB,MAAM,YAAY,CAAC;AAWvD,KAAK,UAAU,mBAAmB,CAAC,GAAc,EAAE,IAAY,EAAE,YAAsB;IACrF,MAAM,UAAU,GAA2B,EAAE,CAAC;IAC9C,KAAK,MAAM,IAAI,IAAI,YAAY,CAAC,KAAK,CAAC,CAAC,EAAE,CAAC,CAAC,EAAE,CAAC;QAC5C,IAAI,CAAC;YACH,MAAM,KAAK,GAAG,MAAM,GAAG,CAAC,IAAI,CAAC,CAAC,IAAI,EAAE,IAAI,EAAE,IAAI,EAAE,IAAI,CAAC,CAAC,CAAC;YACvD,MAAM,WAAW,GAAG,KAAK,CAAC,KAAK,CAAC,yCAAyC,CAAC,CAAC;YAC3E,IAAI,WAAW,EAAE,CAAC;gBAChB,KAAK,MAAM,MAAM,IAAI,WAAW,EAAE,CAAC;oBACjC,MAAM,KAAK,GAAG,wCAAwC,CAAC,IAAI,CAAC,MAAM,CAAC,CAAC;oBACpE,IAAI,KAAK,IAAI,KAAK,CAAC,CAAC,CAAC,EAAE,CAAC;wBACtB,MAAM,SAAS,GAAG,QAAQ,CAAC,KAAK,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC;wBACzC,MAAM,KAAK,GAAG,KAAK,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,QAAQ,CAAC,KAAK,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC;wBACpD,KAAK,IAAI,CAAC,GAAG,SAAS,EAAE,CAAC,GAAG,SAAS,GAAG,IAAI,CAAC,GAAG,CAAC,KAAK,EAAE,CAAC,CAAC,EAAE,CAAC,EAAE,EAAE,CAAC;4BAChE,UAAU,CAAC,CAAC,CAAC,GAAG,CAAC,UAAU,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC,GAAG,CAAC,CAAC;wBAC3C,CAAC;oBACH,CAAC;gBACH,CAAC;YACH,CAAC;QACH,CAAC;QAAC,MAAM,CAAC;YACP,qBAAqB;QACvB,CAAC;IACH,CAAC;IAED,MAAM,KAAK,GAAG,MAAM,CAAC,IAAI,CAAC,UAAU,CAAC,CAAC,GAAG,CAAC,MAAM,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,CAAC,EAAE,EAAE,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC;IACxE,IAAI,KAAK,CAAC,MAAM,KAAK,CAAC,IAAI,KAAK,CAAC,CAAC,CAAC,KAAK,SAAS;QAAE,OAAO,EAAE,CAAC;IAE5D,MAAM,MAAM,GAAa,EAAE,CAAC;IAC5B,IAAI,KAAK,GAAW,KAAK,CAAC,CAAC,CAAC,CAAC;IAC7B,IAAI,IAAI,GAAW,KAAK,CAAC,CAAC,CAAC,CAAC;IAE5B,KAAK,IAAI,CAAC,GAAG,CAAC,EAAE,CAAC,GAAG,KAAK,CAAC,MAAM,EAAE,CAAC,EAAE,EAAE,CAAC;QACtC,MAAM,OAAO,GAAG,KAAK,CAAC,CAAC,CAAC,CAAC;QACzB,IAAI,OAAO,KAAK,SAAS,IAAI,OAAO,KAAK,IAAI,GAAG,CAAC,EAAE,CAAC;YAClD,IAAI,GAAG,OAAO,CAAC;QACjB,CAAC;aAAM,IAAI,OAAO,KAAK,SAAS,EAAE,CAAC;YACjC,MAAM,CAAC,IAAI,CAAC,KAAK,KAAK,IAAI,CAAC,CAAC,CAAC,IAAI,KAAK,EAAE,CAAC,CAAC,CAAC,IAAI,KAAK,IAAI,IAAI,EAAE,CAAC,CAAC;YAChE,KAAK,GAAG,OAAO,CAAC;YAChB,IAAI,GAAG,OAAO,CAAC;QACjB,CAAC;IACH,CAAC;IACD,MAAM,CAAC,IAAI,CAAC,KAAK,KAAK,IAAI,CAAC,CAAC,CAAC,IAAI,KAAK,EAAE,CAAC,CAAC,CAAC,IAAI,KAAK,IAAI,IAAI,EAAE,CAAC,CAAC;IAEhE,OAAO,MAAM,CAAC,KAAK,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC;AAC5B,CAAC;AAED,MAAM,CAAC,KAAK,UAAU,WAAW;IAC/B,MAAM,CAAC,GAAG,OAAO,EAAE,CAAC;IACpB,CAAC,CAAC,KAAK,CAAC,2CAA2C,CAAC,CAAC;IAErD,MAAM,GAAG,GAAG,SAAS,EAAE,CAAC;IACxB,IAAI,GAAG,CAAC;IACR,IAAI,CAAC;QACH,GAAG,GAAG,MAAM,GAAG,CAAC,GAAG,CAAC,EAAE,QAAQ,EAAE,GAAG,EAAE,CAAC,CAAC;IACzC,CAAC;IAAC,MAAM,CAAC;QACP,CAAC,CAAC,IAAI,CAAC,0BAA0B,CAAC,CAAC;QACnC,IAAI,CAAC,KAAK,CAAC,GAAG,CAAC,qFAAqF,CAAC,EAAE,sBAAsB,CAAC,CAAC;QAC/H,OAAO;IACT,CAAC;IAED,MAAM,SAAS,GAA8F,EAAE,CAAC;IAEhH,KAAK,MAAM,MAAM,IAAI,GAAG,CAAC,GAAG,EAAE,CAAC;QAC7B,IAAI,CAAC,MAAM,CAAC,IAAI;YAAE,SAAS;QAC3B,MAAM,QAAQ,GAAG,uBAAuB,CAAC,IAAI,CAAC,MAAM,CAAC,OAAO,CAAC,CAAC;QAE9D,IAAI,CAAC;YACH,MAAM,IAAI,GAAG,MAAM,GAAG,CAAC,IAAI,CAAC,CAAC,aAAa,EAAE,WAAW,EAAE,MAAM,CAAC,IAAI,CAAC,CAAC,CAAC;YACvE,MAAM,KAAK,GAAG,IAAI,CAAC,KAAK,CAAC,IAAI,CAAC,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC,MAAM,CAAC,OAAO,CAAC,CAAC;YAExD,KAAK,MAAM,IAAI,IAAI,KAAK,EAAE,CAAC;gBACzB,IAAI,CAAC,SAAS,CAAC,IAAI,CAAC,EAAE,CAAC;oBACrB,SAAS,CAAC,IAAI,CAAC,GAAG,EAAE,KAAK,EAAE,CAAC,EAAE,QAAQ,EAAE,CAAC,EAAE,QAAQ,EAAE,EAAE,EAAE,MAAM,EAAE,EAAE,EAAE,CAAC;gBACxE,CAAC;gBACD,MAAM,IAAI,GAAG,SAAS,CAAC,IAAI,CAAC,CAAC;gBAC7B,IAAI,CAAC,IAAI;oBAAE,SAAS;gBAEpB,IAAI,CAAC,KAAK,IAAI,CAAC,CAAC;gBAChB,IAAI,QAAQ,EAAE,CAAC;oBACb,IAAI,CAAC,QAAQ,IAAI,CAAC,CAAC;oBACnB,IAAI,CAAC,MAAM,CAAC,IAAI,CAAC,MAAM,CAAC,IAAI,CAAC,CAAC;oBAC9B,MAAM,QAAQ,GAAG,MAAM,CAAC,OAAO,CAAC,KAAK,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC,EAAE,IAAI,EAAE,CAAC;oBACvD,IAAI,QAAQ,IAAI,CAAC,IAAI,CAAC,QAAQ,CAAC,QAAQ,CAAC,QAAQ,CAAC,IAAI,IAAI,CAAC,QAAQ,CAAC,MAAM,GAAG,CAAC,EAAE,CAAC;wBAC9E,IAAI,CAAC,QAAQ,CAAC,IAAI,CAAC,QAAQ,CAAC,CAAC;oBAC/B,CAAC;gBACH,CAAC;YACH,CAAC;QACH,CAAC;QAAC,MAAM,CAAC;YACP,sCAAsC;QACxC,CAAC;IACH,CAAC;IAED,CAAC,CAAC,IAAI,CAAC,8BAA8B,CAAC,CAAC;IAEvC,MAAM,OAAO,GAAgB,MAAM,CAAC,OAAO,CAAC,SAAS,CAAC;SACnD,GAAG,CAAC,CAAC,CAAC,IAAI,EAAE,KAAK,CAAC,EAAE,EAAE;QACrB,IAAI,SAAS,GAA8B,KAAK,CAAC;QACjD,IAAI,KAAK,CAAC,QAAQ,IAAI,CAAC,IAAI,KAAK,CAAC,KAAK,GAAG,EAAE;YAAE,SAAS,GAAG,MAAM,CAAC;aAC3D,IAAI,KAAK,CAAC,QAAQ,IAAI,CAAC,IAAI,KAAK,CAAC,KAAK,GAAG,CAAC;YAAE,SAAS,GAAG,QAAQ,CAAC;QAEtE,OAAO;YACL,IAAI;YACJ,UAAU,EAAE,KAAK,CAAC,KAAK;YACvB,QAAQ,EAAE,KAAK,CAAC,QAAQ;YACxB,SAAS;YACT,cAAc,EAAE,KAAK,CAAC,QAAQ;YAC9B,YAAY,EAAE,KAAK,CAAC,MAAM;SAC3B,CAAC;IACJ,CAAC,CAAC;SACD,MAAM,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,SAAS,KAAK,KAAK,CAAC;SACpC,IAAI,CAAC,CAAC,CAAC,EAAE,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,QAAQ,GAAG,CAAC,CAAC,QAAQ,CAAC,CAAC;IAE3C,IAAI,OAAO,CAAC,MAAM,KAAK,CAAC,EAAE,CAAC;QACzB,IAAI,CAAC,KAAK,CAAC,KAAK,CAAC,8BAA8B,CAAC,EAAE,sBAAsB,CAAC,CAAC;QAC1E,OAAO;IACT,CAAC;IAED,MAAM,UAAU,GAAG,OAAO,CAAC,KAAK,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC;IACvC,MAAM,eAAe,GAAa,EAAE,CAAC;IAErC,KAAK,MAAM,CAAC,IAAI,UAAU,EAAE,CAAC;QAC3B,MAAM,IAAI,GAAG,CAAC,CAAC,SAAS,KAAK,MAAM,CAAC,CAAC,CAAC,KAAK,CAAC,GAAG,CAAC,cAAc,CAAC,CAAC,CAAC,CAAC,KAAK,CAAC,MAAM,CAAC,gBAAgB,CAAC,CAAC;QACjG,IAAI,KAAK,GAAG,GAAG,IAAI,IAAI,KAAK,CAAC,IAAI,CAAC,CAAC,CAAC,IAAI,CAAC,WAAW,CAAC,CAAC,UAAU,aAAa,CAAC,CAAC,QAAQ,oBAAoB,CAAC;QAE5G,MAAM,UAAU,GAAG,MAAM,mBAAmB,CAAC,GAAG,EAAE,CAAC,CAAC,IAAI,EAAE,CAAC,CAAC,YAAY,CAAC,CAAC;QAC1E,IAAI,UAAU,CAAC,MAAM,GAAG,CAAC,EAAE,CAAC;YAC1B,KAAK,IAAI,0BAA0B,KAAK,CAAC,IAAI,CAAC,UAAU,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC,EAAE,CAAC;QACzE,CAAC;QAED,IAAI,CAAC,CAAC,cAAc,CAAC,MAAM,GAAG,CAAC,EAAE,CAAC;YAChC,KAAK,IAAI,uBAAuB,CAAC;YACjC,CAAC,CAAC,cAAc,CAAC,OAAO,CAAC,CAAC,GAAG,EAAE,EAAE;gBAC/B,KAAK,IAAI,aAAa,KAAK,CAAC,GAAG,CAAC,GAAG,CAAC,EAAE,CAAC;YACzC,CAAC,CAAC,CAAC;QACL,CAAC;QAED,eAAe,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC;IAC9B,CAAC;IAED,IAAI,CAAC,eAAe,CAAC,IAAI,CAAC,MAAM,CAAC,EAAE,sBAAsB,CAAC,CAAC;AAC7D,CAAC"}
|
package/bin/src/index.js
CHANGED
|
@@ -18,10 +18,10 @@ program
|
|
|
18
18
|
program
|
|
19
19
|
.name('ft')
|
|
20
20
|
.description('FrontTerrain — The Onboarding Co-Pilot for New Codebases')
|
|
21
|
-
.version('1.0.
|
|
21
|
+
.version('1.0.8');
|
|
22
22
|
// Header UI on execution
|
|
23
23
|
program.hook('preAction', () => {
|
|
24
|
-
intro(chalk.bgCyan.black(' FrontTerrain CLI (v1.0.
|
|
24
|
+
intro(chalk.bgCyan.black(' FrontTerrain CLI (v1.0.8) '));
|
|
25
25
|
});
|
|
26
26
|
// Command definitions
|
|
27
27
|
program
|
|
@@ -43,6 +43,7 @@ program
|
|
|
43
43
|
.description('Show actual file knowledge holders based on git history')
|
|
44
44
|
.action(async (path) => {
|
|
45
45
|
await handleOwners(path);
|
|
46
|
+
outro(chalk.cyan('File ownership analysis complete.'));
|
|
46
47
|
});
|
|
47
48
|
program.parse(process.argv);
|
|
48
49
|
//# sourceMappingURL=index.js.map
|
package/bin/src/index.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.js","sourceRoot":"","sources":["index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,OAAO,EAAE,MAAM,WAAW,CAAC;AACpC,OAAO,EAAE,KAAK,EAAE,KAAK,EAAE,MAAM,gBAAgB,CAAC;AAC9C,OAAO,KAAK,MAAM,OAAO,CAAC;AAC1B,OAAO,EAAE,UAAU,EAAE,MAAM,oBAAoB,CAAC;AAChD,OAAO,EAAE,WAAW,EAAE,MAAM,qBAAqB,CAAC;AAClD,OAAO,EAAE,YAAY,EAAE,MAAM,sBAAsB,CAAC;AACpD,OAAO,EAAE,WAAW,EAAE,MAAM,qBAAqB,CAAC;AAClD,OAAO,EAAE,WAAW,EAAE,MAAM,qBAAqB,CAAC;AAElD,MAAM,OAAO,GAAG,IAAI,OAAO,EAAE,CAAC;AAE9B,OAAO;KACJ,OAAO,CAAC,OAAO,CAAC;KAChB,WAAW,CAAC,qDAAqD,CAAC;KAClE,MAAM,CAAC,KAAK,IAAI,EAAE,CAAC,WAAW,EAAE,CAAC,CAAC;AAErC,OAAO;KACJ,OAAO,CAAC,OAAO,CAAC;KAChB,WAAW,CAAC,kEAAkE,CAAC;KAC/E,MAAM,CAAC,KAAK,IAAI,EAAE,CAAC,WAAW,EAAE,CAAC,CAAC;AAGrC,OAAO;KACJ,IAAI,CAAC,IAAI,CAAC;KACV,WAAW,CAAC,0DAA0D,CAAC;KACvE,OAAO,CAAC,OAAO,CAAC,CAAC;AAEpB,yBAAyB;AACzB,OAAO,CAAC,IAAI,CAAC,WAAW,EAAE,GAAG,EAAE;IAC7B,KAAK,CAAC,KAAK,CAAC,MAAM,CAAC,KAAK,CAAC,6BAA6B,CAAC,CAAC,CAAC;AAC3D,CAAC,CAAC,CAAC;AAEH,sBAAsB;AACtB,OAAO;KACJ,OAAO,CAAC,gBAAgB,CAAC;KACzB,WAAW,CAAC,iDAAiD,CAAC;KAC9D,MAAM,CAAC,KAAK,EAAE,OAAO,EAAE,EAAE;IACxB,MAAM,UAAU,CAAC,OAAO,CAAC,CAAC;IAC1B,KAAK,CAAC,KAAK,CAAC,KAAK,CAAC,yDAAyD,CAAC,CAAC,CAAC;AAChF,CAAC,CAAC,CAAC;AAEL,OAAO;KACJ,OAAO,CAAC,OAAO,CAAC;KAChB,WAAW,CAAC,yCAAyC,CAAC;KACtD,MAAM,CAAC,KAAK,IAAI,EAAE;IACjB,MAAM,WAAW,EAAE,CAAC;IACpB,KAAK,CAAC,KAAK,CAAC,IAAI,CAAC,4BAA4B,CAAC,CAAC,CAAC;AAClD,CAAC,CAAC,CAAC;AAEL,OAAO;KACJ,OAAO,CAAC,eAAe,CAAC;KACxB,WAAW,CAAC,yDAAyD,CAAC;KACtE,MAAM,CAAC,KAAK,EAAE,IAAI,EAAE,EAAE;IACrB,MAAM,YAAY,CAAC,IAAI,CAAC,CAAC;
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,OAAO,EAAE,MAAM,WAAW,CAAC;AACpC,OAAO,EAAE,KAAK,EAAE,KAAK,EAAE,MAAM,gBAAgB,CAAC;AAC9C,OAAO,KAAK,MAAM,OAAO,CAAC;AAC1B,OAAO,EAAE,UAAU,EAAE,MAAM,oBAAoB,CAAC;AAChD,OAAO,EAAE,WAAW,EAAE,MAAM,qBAAqB,CAAC;AAClD,OAAO,EAAE,YAAY,EAAE,MAAM,sBAAsB,CAAC;AACpD,OAAO,EAAE,WAAW,EAAE,MAAM,qBAAqB,CAAC;AAClD,OAAO,EAAE,WAAW,EAAE,MAAM,qBAAqB,CAAC;AAElD,MAAM,OAAO,GAAG,IAAI,OAAO,EAAE,CAAC;AAE9B,OAAO;KACJ,OAAO,CAAC,OAAO,CAAC;KAChB,WAAW,CAAC,qDAAqD,CAAC;KAClE,MAAM,CAAC,KAAK,IAAI,EAAE,CAAC,WAAW,EAAE,CAAC,CAAC;AAErC,OAAO;KACJ,OAAO,CAAC,OAAO,CAAC;KAChB,WAAW,CAAC,kEAAkE,CAAC;KAC/E,MAAM,CAAC,KAAK,IAAI,EAAE,CAAC,WAAW,EAAE,CAAC,CAAC;AAGrC,OAAO;KACJ,IAAI,CAAC,IAAI,CAAC;KACV,WAAW,CAAC,0DAA0D,CAAC;KACvE,OAAO,CAAC,OAAO,CAAC,CAAC;AAEpB,yBAAyB;AACzB,OAAO,CAAC,IAAI,CAAC,WAAW,EAAE,GAAG,EAAE;IAC7B,KAAK,CAAC,KAAK,CAAC,MAAM,CAAC,KAAK,CAAC,6BAA6B,CAAC,CAAC,CAAC;AAC3D,CAAC,CAAC,CAAC;AAEH,sBAAsB;AACtB,OAAO;KACJ,OAAO,CAAC,gBAAgB,CAAC;KACzB,WAAW,CAAC,iDAAiD,CAAC;KAC9D,MAAM,CAAC,KAAK,EAAE,OAAO,EAAE,EAAE;IACxB,MAAM,UAAU,CAAC,OAAO,CAAC,CAAC;IAC1B,KAAK,CAAC,KAAK,CAAC,KAAK,CAAC,yDAAyD,CAAC,CAAC,CAAC;AAChF,CAAC,CAAC,CAAC;AAEL,OAAO;KACJ,OAAO,CAAC,OAAO,CAAC;KAChB,WAAW,CAAC,yCAAyC,CAAC;KACtD,MAAM,CAAC,KAAK,IAAI,EAAE;IACjB,MAAM,WAAW,EAAE,CAAC;IACpB,KAAK,CAAC,KAAK,CAAC,IAAI,CAAC,4BAA4B,CAAC,CAAC,CAAC;AAClD,CAAC,CAAC,CAAC;AAEL,OAAO;KACJ,OAAO,CAAC,eAAe,CAAC;KACxB,WAAW,CAAC,yDAAyD,CAAC;KACtE,MAAM,CAAC,KAAK,EAAE,IAAI,EAAE,EAAE;IACrB,MAAM,YAAY,CAAC,IAAI,CAAC,CAAC;IACzB,KAAK,CAAC,KAAK,CAAC,IAAI,CAAC,mCAAmC,CAAC,CAAC,CAAC;AACzD,CAAC,CAAC,CAAC;AAEL,OAAO,CAAC,KAAK,CAAC,OAAO,CAAC,IAAI,CAAC,CAAC"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"config.d.ts","sourceRoot":"","sources":["config.ts"],"names":[],"mappings":"AAGA,MAAM,WAAW,WAAW;IAC1B,WAAW,EAAE,MAAM,CAAC;IACpB,KAAK,EAAE,MAAM,CAAC;IACd,UAAU,EAAE,MAAM,CAAC;IACnB,UAAU,EAAE,MAAM,EAAE,CAAC;IACrB,SAAS,EAAE,MAAM,EAAE,CAAC;IACpB,SAAS,EAAE,MAAM,CAAC;CACnB;AAKD,wBAAgB,WAAW,CAAC,IAAI,EAAE,WAAW,QAK5C;AAED,wBAAgB,WAAW,IAAI,WAAW,GAAG,IAAI,CAGhD"}
|
|
1
|
+
{"version":3,"file":"config.d.ts","sourceRoot":"","sources":["config.ts"],"names":[],"mappings":"AAGA,MAAM,WAAW,WAAW;IAC1B,WAAW,EAAE,MAAM,CAAC;IACpB,OAAO,CAAC,EAAE,MAAM,GAAG,SAAS,CAAC;IAC7B,SAAS,CAAC,EAAE,MAAM,GAAG,SAAS,CAAC;IAC/B,KAAK,EAAE,MAAM,CAAC;IACd,UAAU,EAAE,MAAM,CAAC;IACnB,UAAU,EAAE,MAAM,EAAE,CAAC;IACrB,SAAS,EAAE,MAAM,EAAE,CAAC;IACpB,SAAS,EAAE,MAAM,CAAC;CACnB;AAKD,wBAAgB,WAAW,CAAC,IAAI,EAAE,WAAW,QAK5C;AAED,wBAAgB,WAAW,IAAI,WAAW,GAAG,IAAI,CAGhD"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"config.js","sourceRoot":"","sources":["config.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,MAAM,IAAI,CAAC;AACpB,OAAO,IAAI,MAAM,MAAM,CAAC;
|
|
1
|
+
{"version":3,"file":"config.js","sourceRoot":"","sources":["config.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,MAAM,IAAI,CAAC;AACpB,OAAO,IAAI,MAAM,MAAM,CAAC;AAaxB,MAAM,MAAM,GAAG,IAAI,CAAC,IAAI,CAAC,OAAO,CAAC,GAAG,EAAE,EAAE,KAAK,CAAC,CAAC;AAC/C,MAAM,YAAY,GAAG,IAAI,CAAC,IAAI,CAAC,MAAM,EAAE,cAAc,CAAC,CAAC;AAEvD,MAAM,UAAU,WAAW,CAAC,IAAiB;IAC3C,IAAI,CAAC,EAAE,CAAC,UAAU,CAAC,MAAM,CAAC,EAAE,CAAC;QAC3B,EAAE,CAAC,SAAS,CAAC,MAAM,EAAE,EAAE,SAAS,EAAE,IAAI,EAAE,CAAC,CAAC;IAC5C,CAAC;IACD,EAAE,CAAC,aAAa,CAAC,YAAY,EAAE,IAAI,CAAC,SAAS,CAAC,IAAI,EAAE,IAAI,EAAE,CAAC,CAAC,CAAC,CAAC;AAChE,CAAC;AAED,MAAM,UAAU,WAAW;IACzB,IAAI,CAAC,EAAE,CAAC,UAAU,CAAC,YAAY,CAAC;QAAE,OAAO,IAAI,CAAC;IAC9C,OAAO,IAAI,CAAC,KAAK,CAAC,EAAE,CAAC,YAAY,CAAC,YAAY,EAAE,OAAO,CAAC,CAAC,CAAC;AAC5D,CAAC"}
|
|
@@ -0,0 +1,223 @@
|
|
|
1
|
+
<!DOCTYPE html>
|
|
2
|
+
<html lang="en">
|
|
3
|
+
<head>
|
|
4
|
+
<meta charset="utf-8">
|
|
5
|
+
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
|
6
|
+
<title>Introducing FrontTerrain (ft-scout)</title>
|
|
7
|
+
<style>
|
|
8
|
+
body {
|
|
9
|
+
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Helvetica, Arial, sans-serif;
|
|
10
|
+
background-color: #0f172a;
|
|
11
|
+
margin: 0;
|
|
12
|
+
padding: 40px 12px;
|
|
13
|
+
color: #e2e8f0;
|
|
14
|
+
}
|
|
15
|
+
.container {
|
|
16
|
+
max-width: 620px;
|
|
17
|
+
margin: 0 auto;
|
|
18
|
+
background: #1e293b;
|
|
19
|
+
border-radius: 16px;
|
|
20
|
+
overflow: hidden;
|
|
21
|
+
box-shadow: 0 10px 30px rgba(0, 0, 0, 0.4);
|
|
22
|
+
border: 1px solid #334155;
|
|
23
|
+
}
|
|
24
|
+
.header {
|
|
25
|
+
background: linear-gradient(135deg, #0284c7 0%, #0d9488 100%);
|
|
26
|
+
padding: 40px 30px;
|
|
27
|
+
text-align: center;
|
|
28
|
+
color: #ffffff;
|
|
29
|
+
}
|
|
30
|
+
.badge {
|
|
31
|
+
display: inline-block;
|
|
32
|
+
background: rgba(255, 255, 255, 0.2);
|
|
33
|
+
color: #ffffff;
|
|
34
|
+
font-size: 11px;
|
|
35
|
+
font-weight: 700;
|
|
36
|
+
padding: 4px 14px;
|
|
37
|
+
border-radius: 20px;
|
|
38
|
+
text-transform: uppercase;
|
|
39
|
+
letter-spacing: 1px;
|
|
40
|
+
margin-bottom: 12px;
|
|
41
|
+
}
|
|
42
|
+
.header h1 {
|
|
43
|
+
margin: 0;
|
|
44
|
+
font-size: 28px;
|
|
45
|
+
font-weight: 800;
|
|
46
|
+
letter-spacing: -0.5px;
|
|
47
|
+
}
|
|
48
|
+
.header p {
|
|
49
|
+
margin-top: 10px;
|
|
50
|
+
color: #e0f2fe;
|
|
51
|
+
font-size: 16px;
|
|
52
|
+
font-weight: 400;
|
|
53
|
+
}
|
|
54
|
+
.content {
|
|
55
|
+
padding: 36px 30px;
|
|
56
|
+
}
|
|
57
|
+
.intro {
|
|
58
|
+
font-size: 16px;
|
|
59
|
+
line-height: 1.7;
|
|
60
|
+
color: #cbd5e1;
|
|
61
|
+
margin-bottom: 28px;
|
|
62
|
+
}
|
|
63
|
+
.section-title {
|
|
64
|
+
font-size: 18px;
|
|
65
|
+
font-weight: 700;
|
|
66
|
+
color: #f8fafc;
|
|
67
|
+
margin-bottom: 16px;
|
|
68
|
+
display: flex;
|
|
69
|
+
align-items: center;
|
|
70
|
+
}
|
|
71
|
+
.feature-card {
|
|
72
|
+
background: #0f172a;
|
|
73
|
+
border: 1px solid #334155;
|
|
74
|
+
border-radius: 12px;
|
|
75
|
+
padding: 20px;
|
|
76
|
+
margin-bottom: 16px;
|
|
77
|
+
transition: transform 0.2s;
|
|
78
|
+
}
|
|
79
|
+
.feature-header {
|
|
80
|
+
display: flex;
|
|
81
|
+
align-items: center;
|
|
82
|
+
margin-bottom: 8px;
|
|
83
|
+
}
|
|
84
|
+
.command-tag {
|
|
85
|
+
background: #0284c7;
|
|
86
|
+
color: #ffffff;
|
|
87
|
+
font-family: 'SFMono-Regular', Consolas, 'Liberation Mono', Menlo, monospace;
|
|
88
|
+
font-weight: 700;
|
|
89
|
+
font-size: 13px;
|
|
90
|
+
padding: 4px 10px;
|
|
91
|
+
border-radius: 6px;
|
|
92
|
+
margin-right: 10px;
|
|
93
|
+
}
|
|
94
|
+
.feature-name {
|
|
95
|
+
font-weight: 700;
|
|
96
|
+
font-size: 16px;
|
|
97
|
+
color: #f8fafc;
|
|
98
|
+
}
|
|
99
|
+
.feature-desc {
|
|
100
|
+
font-size: 14px;
|
|
101
|
+
color: #94a3b8;
|
|
102
|
+
margin: 0;
|
|
103
|
+
line-height: 1.6;
|
|
104
|
+
}
|
|
105
|
+
.code-box {
|
|
106
|
+
background: #090d16;
|
|
107
|
+
color: #38bdf8;
|
|
108
|
+
padding: 20px;
|
|
109
|
+
border-radius: 10px;
|
|
110
|
+
font-family: 'SFMono-Regular', Consolas, 'Liberation Mono', Menlo, monospace;
|
|
111
|
+
font-size: 14px;
|
|
112
|
+
line-height: 1.8;
|
|
113
|
+
margin: 28px 0;
|
|
114
|
+
border: 1px solid #1e293b;
|
|
115
|
+
}
|
|
116
|
+
.code-box span.comment {
|
|
117
|
+
color: #64748b;
|
|
118
|
+
}
|
|
119
|
+
.code-box span.cmd {
|
|
120
|
+
color: #4ade80;
|
|
121
|
+
}
|
|
122
|
+
.cta-container {
|
|
123
|
+
text-align: center;
|
|
124
|
+
margin: 36px 0 20px 0;
|
|
125
|
+
}
|
|
126
|
+
.btn {
|
|
127
|
+
background: linear-gradient(135deg, #0284c7 0%, #0d9488 100%);
|
|
128
|
+
color: #ffffff !important;
|
|
129
|
+
padding: 16px 36px;
|
|
130
|
+
border-radius: 10px;
|
|
131
|
+
text-decoration: none;
|
|
132
|
+
font-weight: 700;
|
|
133
|
+
display: inline-block;
|
|
134
|
+
font-size: 16px;
|
|
135
|
+
box-shadow: 0 4px 14px rgba(2, 132, 199, 0.4);
|
|
136
|
+
}
|
|
137
|
+
.footer {
|
|
138
|
+
background: #0f172a;
|
|
139
|
+
text-align: center;
|
|
140
|
+
padding: 24px;
|
|
141
|
+
font-size: 13px;
|
|
142
|
+
color: #64748b;
|
|
143
|
+
border-top: 1px solid #334155;
|
|
144
|
+
}
|
|
145
|
+
.footer a {
|
|
146
|
+
color: #38bdf8;
|
|
147
|
+
text-decoration: none;
|
|
148
|
+
}
|
|
149
|
+
</style>
|
|
150
|
+
</head>
|
|
151
|
+
<body>
|
|
152
|
+
<div class="container">
|
|
153
|
+
<div class="header">
|
|
154
|
+
<span class="badge">V1.0.8 RELEASE</span>
|
|
155
|
+
<h1>FrontTerrain (<code>ft-scout</code>)</h1>
|
|
156
|
+
<p>The AI Onboarding Co-Pilot for New Codebases</p>
|
|
157
|
+
</div>
|
|
158
|
+
|
|
159
|
+
<div class="content">
|
|
160
|
+
<p class="intro">
|
|
161
|
+
Hey FrontTerrain Developer 👋,<br><br>
|
|
162
|
+
Jumping into a massive or legacy codebase can be intimidating. You waste hours deciphering complex file structures, guessing who owns which module, or accidentally editing fragile hotspots.<br><br>
|
|
163
|
+
That’s why we built <strong><code>ft-scout</code> (FrontTerrain CLI)</strong> — your AI co-pilot designed to give you instant codebase clarity directly inside your terminal!
|
|
164
|
+
</p>
|
|
165
|
+
|
|
166
|
+
<div class="section-title">⚡ What You Can Do with <code>ft-scout</code>:</div>
|
|
167
|
+
|
|
168
|
+
<div class="feature-card">
|
|
169
|
+
<div class="feature-header">
|
|
170
|
+
<span class="command-tag">ft brief</span>
|
|
171
|
+
<span class="feature-name">AI Onboarding Summary</span>
|
|
172
|
+
</div>
|
|
173
|
+
<p class="feature-desc">Get an instant, human-readable summary of repository architecture, tech stack, and key modules so you can start contributing immediately.</p>
|
|
174
|
+
</div>
|
|
175
|
+
|
|
176
|
+
<div class="feature-card">
|
|
177
|
+
<div class="feature-header">
|
|
178
|
+
<span class="command-tag">ft risky</span>
|
|
179
|
+
<span class="feature-name">Code Fragility Risk Map</span>
|
|
180
|
+
</div>
|
|
181
|
+
<p class="feature-desc">Analyzes commit churn, hotfixes, and historical regressions to alert you about high-risk hotspots and line ranges before you touch them.</p>
|
|
182
|
+
</div>
|
|
183
|
+
|
|
184
|
+
<div class="feature-card">
|
|
185
|
+
<div class="feature-header">
|
|
186
|
+
<span class="command-tag">ft owners <path></span>
|
|
187
|
+
<span class="feature-name">Domain Knowledge Holders</span>
|
|
188
|
+
</div>
|
|
189
|
+
<p class="feature-desc">Identifies true code experts based on real commit frequency and authorship—so you know exactly who to loop into code reviews.</p>
|
|
190
|
+
</div>
|
|
191
|
+
|
|
192
|
+
<div class="feature-card">
|
|
193
|
+
<div class="feature-header">
|
|
194
|
+
<span class="command-tag">ft setup</span>
|
|
195
|
+
<span class="feature-name">Local Dev Diagnostic</span>
|
|
196
|
+
</div>
|
|
197
|
+
<p class="feature-desc">Automatically scans missing <code>.env</code> keys, node runtime mismatches, and configuration issues before they break your local build.</p>
|
|
198
|
+
</div>
|
|
199
|
+
|
|
200
|
+
<div class="section-title" style="margin-top: 32px;">🚀 Quick Start Guide</div>
|
|
201
|
+
|
|
202
|
+
<div class="code-box">
|
|
203
|
+
<span class="comment"># 1. Install ft-scout globally via npm</span><br>
|
|
204
|
+
<span class="cmd">npm install -g ft-scout</span><br><br>
|
|
205
|
+
<span class="comment"># 2. Run inside any Git codebase</span><br>
|
|
206
|
+
<span class="cmd">ft init</span><br><br>
|
|
207
|
+
<span class="comment"># 3. Get your onboarding brief & risk map</span><br>
|
|
208
|
+
<span class="cmd">ft brief</span><br>
|
|
209
|
+
<span class="cmd">ft risky</span>
|
|
210
|
+
</div>
|
|
211
|
+
|
|
212
|
+
<div class="cta-container">
|
|
213
|
+
<a href="https://github.com/Front-Terrain/FT-Check#readme" class="btn" target="_blank">Explore Documentation & GitHub</a>
|
|
214
|
+
</div>
|
|
215
|
+
</div>
|
|
216
|
+
|
|
217
|
+
<div class="footer">
|
|
218
|
+
<p>Built with ❤️ by Front Terrain</p>
|
|
219
|
+
<p><a href="https://github.com/Front-Terrain/FT-Check">GitHub Repository</a> | <a href="https://github.com/Front-Terrain/FT-Check/issues">Report Issues</a></p>
|
|
220
|
+
</div>
|
|
221
|
+
</div>
|
|
222
|
+
</body>
|
|
223
|
+
</html>
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
Subject: 🚀 Introducing FrontTerrain (ft-scout): Your AI Onboarding Co-Pilot for Codebases
|
|
2
|
+
Preheader: Instant codebase summaries, fragility risk maps, and knowledge owner detection in your terminal.
|
|
3
|
+
|
|
4
|
+
Hey FrontTerrain Developer 👋,
|
|
5
|
+
|
|
6
|
+
Jumping into a massive or legacy codebase can be intimidating. You waste hours deciphering complex file structures, guessing who owns which module, or accidentally editing fragile hotspots.
|
|
7
|
+
|
|
8
|
+
That’s why we built ft-scout (FrontTerrain CLI v1.0.7) — your AI co-pilot designed to give you instant codebase clarity directly inside your terminal!
|
|
9
|
+
|
|
10
|
+
---
|
|
11
|
+
|
|
12
|
+
⚡ WHAT YOU CAN DO WITH FT-SCOUT:
|
|
13
|
+
|
|
14
|
+
1. ft brief — AI Onboarding Summary
|
|
15
|
+
Get an instant, human-readable summary of repository architecture, tech stack, and key modules so you can start contributing immediately.
|
|
16
|
+
|
|
17
|
+
2. ft risky — Code Fragility Risk Map
|
|
18
|
+
Analyzes commit churn, hotfixes, and historical regressions to alert you about high-risk hotspots and line ranges before you touch them.
|
|
19
|
+
|
|
20
|
+
3. ft owners <path> — Domain Knowledge Holders
|
|
21
|
+
Identifies true code experts based on real commit frequency and authorship—so you know exactly who to loop into code reviews.
|
|
22
|
+
|
|
23
|
+
4. ft setup — Local Dev Diagnostic
|
|
24
|
+
Automatically scans missing .env keys, node runtime mismatches, and configuration issues before they break your local build.
|
|
25
|
+
|
|
26
|
+
---
|
|
27
|
+
|
|
28
|
+
🚀 QUICK START GUIDE:
|
|
29
|
+
|
|
30
|
+
1. Install ft-scout globally via npm:
|
|
31
|
+
$ npm install -g ft-scout
|
|
32
|
+
|
|
33
|
+
2. Run inside any Git codebase:
|
|
34
|
+
$ ft init
|
|
35
|
+
|
|
36
|
+
3. Get your onboarding brief & risk map:
|
|
37
|
+
$ ft brief
|
|
38
|
+
$ ft risky
|
|
39
|
+
|
|
40
|
+
---
|
|
41
|
+
|
|
42
|
+
🔗 GitHub & Documentation: https://github.com/Front-Terrain/FT-Check#readme
|
|
43
|
+
|
|
44
|
+
Happy Coding! 🚀
|
|
45
|
+
Front Terrain Team
|
package/package.json
CHANGED
|
@@ -0,0 +1,167 @@
|
|
|
1
|
+
const fs = require('fs');
|
|
2
|
+
const path = require('path');
|
|
3
|
+
|
|
4
|
+
// Try requiring nodemailer from FrontTerrain if not in current node_modules
|
|
5
|
+
let nodemailer;
|
|
6
|
+
try {
|
|
7
|
+
nodemailer = require('nodemailer');
|
|
8
|
+
} catch {
|
|
9
|
+
try {
|
|
10
|
+
nodemailer = require('C:/Users/rishi/FrontTerrain/node_modules/nodemailer');
|
|
11
|
+
} catch {
|
|
12
|
+
nodemailer = require('C:/Users/rishi/FrontTerrain/functions/node_modules/nodemailer');
|
|
13
|
+
}
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
// Try requiring firebase-admin from FrontTerrain
|
|
17
|
+
let admin;
|
|
18
|
+
try {
|
|
19
|
+
admin = require('firebase-admin');
|
|
20
|
+
} catch {
|
|
21
|
+
try {
|
|
22
|
+
admin = require('C:/Users/rishi/FrontTerrain/node_modules/firebase-admin');
|
|
23
|
+
} catch {
|
|
24
|
+
admin = require('C:/Users/rishi/FrontTerrain/functions/node_modules/firebase-admin');
|
|
25
|
+
}
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
const serviceAccountPath = 'C:/Users/rishi/FrontTerrain/ServiceAccountKey.json';
|
|
29
|
+
|
|
30
|
+
const BREVO_USER = 'b23077001@smtp-brevo.com';
|
|
31
|
+
const BREVO_PASS = 'xsmtpsib-63ad84e2307ca830b499c8921f1a242cab01b6f938119cf453e81ab854f643a3-RLpSOgjpmtGZN1NT';
|
|
32
|
+
const FROM_EMAIL = '"FrontTerrain" <noreply@frontterrain.com>';
|
|
33
|
+
const SUBJECT = '🚀 Introducing FrontTerrain (ft-scout): Your AI Onboarding Co-Pilot for Codebases';
|
|
34
|
+
|
|
35
|
+
const htmlPath = path.join(__dirname, '../email-template.html');
|
|
36
|
+
const textPath = path.join(__dirname, '../email-template.txt');
|
|
37
|
+
|
|
38
|
+
const htmlContent = fs.readFileSync(htmlPath, 'utf8');
|
|
39
|
+
const textContent = fs.readFileSync(textPath, 'utf8');
|
|
40
|
+
|
|
41
|
+
const transporter = nodemailer.createTransport({
|
|
42
|
+
host: 'smtp-relay.brevo.com',
|
|
43
|
+
port: 587,
|
|
44
|
+
secure: false,
|
|
45
|
+
auth: {
|
|
46
|
+
user: BREVO_USER,
|
|
47
|
+
pass: BREVO_PASS,
|
|
48
|
+
},
|
|
49
|
+
});
|
|
50
|
+
|
|
51
|
+
async function getAllUserEmails() {
|
|
52
|
+
const emails = new Set();
|
|
53
|
+
|
|
54
|
+
if (fs.existsSync(serviceAccountPath)) {
|
|
55
|
+
try {
|
|
56
|
+
const serviceAccount = require(serviceAccountPath);
|
|
57
|
+
if (!admin.apps.length) {
|
|
58
|
+
admin.initializeApp({
|
|
59
|
+
credential: admin.credential.cert(serviceAccount),
|
|
60
|
+
});
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
// Fetch from Firestore users collection
|
|
64
|
+
const db = admin.firestore();
|
|
65
|
+
const snapshot = await db.collection('users').get();
|
|
66
|
+
snapshot.forEach((doc) => {
|
|
67
|
+
const data = doc.data();
|
|
68
|
+
if (data.email) emails.add(data.email.trim().toLowerCase());
|
|
69
|
+
});
|
|
70
|
+
|
|
71
|
+
// Fetch from Firebase Auth users list
|
|
72
|
+
let nextPageToken;
|
|
73
|
+
do {
|
|
74
|
+
const listUsersResult = await admin.auth().listUsers(1000, nextPageToken);
|
|
75
|
+
listUsersResult.users.forEach((userRecord) => {
|
|
76
|
+
if (userRecord.email) emails.add(userRecord.email.trim().toLowerCase());
|
|
77
|
+
});
|
|
78
|
+
nextPageToken = listUsersResult.pageToken;
|
|
79
|
+
} while (nextPageToken);
|
|
80
|
+
} catch (err) {
|
|
81
|
+
console.warn('⚠️ Could not fetch users from Firebase:', err.message);
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
// Also check if local users.json exists
|
|
86
|
+
const usersJsonPath = path.join(__dirname, '../users.json');
|
|
87
|
+
if (fs.existsSync(usersJsonPath)) {
|
|
88
|
+
try {
|
|
89
|
+
const localUsers = JSON.parse(fs.readFileSync(usersJsonPath, 'utf8'));
|
|
90
|
+
localUsers.forEach((e) => emails.add(e.trim().toLowerCase()));
|
|
91
|
+
} catch (e) {}
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
return Array.from(emails);
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
async function main() {
|
|
98
|
+
const args = process.argv.slice(2);
|
|
99
|
+
const isSendMode = args.includes('--send');
|
|
100
|
+
const testEmailArg = args.find((a) => a.startsWith('--test='));
|
|
101
|
+
const testEmail = testEmailArg ? testEmailArg.split('=')[1] : null;
|
|
102
|
+
|
|
103
|
+
console.log('\n=================================================');
|
|
104
|
+
console.log('🚀 FrontTerrain ft-scout Brevo Email Dispatcher');
|
|
105
|
+
console.log('=================================================\n');
|
|
106
|
+
|
|
107
|
+
if (testEmail) {
|
|
108
|
+
console.log(`✉️ Sending test email to: ${testEmail}...`);
|
|
109
|
+
const info = await transporter.sendMail({
|
|
110
|
+
from: FROM_EMAIL,
|
|
111
|
+
to: testEmail,
|
|
112
|
+
subject: `${SUBJECT}`,
|
|
113
|
+
html: htmlContent,
|
|
114
|
+
text: textContent,
|
|
115
|
+
});
|
|
116
|
+
console.log(`✅ Test email sent successfully! MessageID: ${info.messageId || info.response}`);
|
|
117
|
+
return;
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
console.log('🔍 Fetching all FT users...');
|
|
121
|
+
const recipients = await getAllUserEmails();
|
|
122
|
+
console.log(`📊 Found ${recipients.length} total FT user email address(es):\n`);
|
|
123
|
+
recipients.forEach((email, i) => console.log(` ${i + 1}. ${email}`));
|
|
124
|
+
|
|
125
|
+
if (recipients.length === 0) {
|
|
126
|
+
console.log('\n⚠️ No user emails found. Please verify Firebase connection or users.json file.');
|
|
127
|
+
return;
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
if (!isSendMode) {
|
|
131
|
+
console.log('\n-------------------------------------------------');
|
|
132
|
+
console.log('📌 DRY-RUN MODE (No emails sent).');
|
|
133
|
+
console.log('To send a test email first, run:');
|
|
134
|
+
console.log(' node scripts/send-brevo-campaign.cjs --test=your_email@domain.com\n');
|
|
135
|
+
console.log('To send to ALL users above, run:');
|
|
136
|
+
console.log(' node scripts/send-brevo-campaign.cjs --send');
|
|
137
|
+
console.log('-------------------------------------------------\n');
|
|
138
|
+
return;
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
console.log('\n🚀 Starting bulk email dispatch via Brevo SMTP...');
|
|
142
|
+
let successCount = 0;
|
|
143
|
+
let failCount = 0;
|
|
144
|
+
|
|
145
|
+
for (const email of recipients) {
|
|
146
|
+
try {
|
|
147
|
+
const info = await transporter.sendMail({
|
|
148
|
+
from: FROM_EMAIL,
|
|
149
|
+
to: email,
|
|
150
|
+
subject: SUBJECT,
|
|
151
|
+
html: htmlContent,
|
|
152
|
+
text: textContent,
|
|
153
|
+
});
|
|
154
|
+
successCount++;
|
|
155
|
+
console.log(`✅ [${successCount}/${recipients.length}] Sent to ${email} (MessageID: ${info.messageId || info.response})`);
|
|
156
|
+
} catch (err) {
|
|
157
|
+
failCount++;
|
|
158
|
+
console.error(`❌ Failed to send to ${email}:`, err.message);
|
|
159
|
+
}
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
console.log('\n=================================================');
|
|
163
|
+
console.log(`🎉 Campaign Complete: ${successCount} Sent, ${failCount} Failed.`);
|
|
164
|
+
console.log('=================================================\n');
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
main().catch(console.error);
|
|
@@ -0,0 +1,77 @@
|
|
|
1
|
+
import fs from 'fs';
|
|
2
|
+
import path from 'path';
|
|
3
|
+
import { fileURLToPath } from 'url';
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* FrontTerrain (ft-scout) Email Dispatch Script
|
|
7
|
+
*
|
|
8
|
+
* Usage:
|
|
9
|
+
* 1. Set environment variables:
|
|
10
|
+
* export RESEND_API_KEY="re_123456789"
|
|
11
|
+
* (or SENDGRID_API_KEY="SG.xxx")
|
|
12
|
+
* 2. Create `users.json` containing an array of emails:
|
|
13
|
+
* ["user1@example.com", "user2@example.com"]
|
|
14
|
+
* 3. Run: node scripts/send-email.js
|
|
15
|
+
*/
|
|
16
|
+
|
|
17
|
+
const __filename = fileURLToPath(import.meta.url);
|
|
18
|
+
const __dirname = path.dirname(__filename);
|
|
19
|
+
|
|
20
|
+
const htmlTemplatePath = path.join(__dirname, '../email-template.html');
|
|
21
|
+
const textTemplatePath = path.join(__dirname, '../email-template.txt');
|
|
22
|
+
|
|
23
|
+
const htmlContent = fs.readFileSync(htmlTemplatePath, 'utf8');
|
|
24
|
+
const textContent = fs.readFileSync(textTemplatePath, 'utf8');
|
|
25
|
+
|
|
26
|
+
async function sendEmailResend(apiKey, recipients) {
|
|
27
|
+
console.log(`Sending email to ${recipients.length} recipients via Resend API...`);
|
|
28
|
+
|
|
29
|
+
for (const email of recipients) {
|
|
30
|
+
try {
|
|
31
|
+
const response = await fetch('https://api.resend.com/emails', {
|
|
32
|
+
method: 'POST',
|
|
33
|
+
headers: {
|
|
34
|
+
'Authorization': `Bearer ${apiKey}`,
|
|
35
|
+
'Content-Type': 'application/json'
|
|
36
|
+
},
|
|
37
|
+
body: JSON.stringify({
|
|
38
|
+
from: 'FrontTerrain <updates@frontterrain.com>',
|
|
39
|
+
to: [email],
|
|
40
|
+
subject: '🚀 Introducing FrontTerrain (ft-scout): Your AI Onboarding Co-Pilot',
|
|
41
|
+
html: htmlContent,
|
|
42
|
+
text: textContent
|
|
43
|
+
})
|
|
44
|
+
});
|
|
45
|
+
const data = await response.json();
|
|
46
|
+
if (response.ok) {
|
|
47
|
+
console.log(`✅ Sent to ${email} (ID: ${data.id})`);
|
|
48
|
+
} else {
|
|
49
|
+
console.error(`❌ Failed to send to ${email}:`, data);
|
|
50
|
+
}
|
|
51
|
+
} catch (err) {
|
|
52
|
+
console.error(`❌ Error sending to ${email}:`, err.message);
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
async function main() {
|
|
58
|
+
const usersFilePath = path.join(__dirname, '../users.json');
|
|
59
|
+
if (!fs.existsSync(usersFilePath)) {
|
|
60
|
+
console.log('ℹ️ No users.json file found. Creating example users.json...');
|
|
61
|
+
fs.writeFileSync(usersFilePath, JSON.stringify(["user1@example.com", "user2@example.com"], null, 2));
|
|
62
|
+
console.log('📝 Please edit `users.json` with your real user emails list.');
|
|
63
|
+
return;
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
const recipients = JSON.parse(fs.readFileSync(usersFilePath, 'utf8'));
|
|
67
|
+
const resendApiKey = process.env.RESEND_API_KEY;
|
|
68
|
+
|
|
69
|
+
if (resendApiKey) {
|
|
70
|
+
await sendEmailResend(resendApiKey, recipients);
|
|
71
|
+
} else {
|
|
72
|
+
console.log('\n⚠️ No API Key detected.');
|
|
73
|
+
console.log('Set `RESEND_API_KEY` or copy the content from `email-template.html` / `email-template.txt` into Mailchimp/SendGrid/Brevo.');
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
main().catch(console.error);
|