ft-scout 1.0.9 → 1.1.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/.firebase/hosting.d2Vi.cache +9 -0
- package/.firebaserc +14 -0
- package/AUTONOMOUS_FEATURES.md +192 -0
- package/README.md +482 -29
- package/bin/ft.js +2 -0
- package/bin/src/commands/agent.d.ts.map +1 -0
- package/bin/src/commands/agent.js +1230 -0
- package/bin/src/commands/agent.js.map +1 -0
- package/bin/src/commands/audit.d.ts.map +1 -0
- package/bin/src/commands/audit.js +104 -0
- package/bin/src/commands/audit.js.map +1 -0
- package/bin/src/commands/authCommand.d.ts.map +1 -0
- package/bin/src/commands/authCommand.js +38 -0
- package/bin/src/commands/authCommand.js.map +1 -0
- package/bin/src/commands/benchmark.d.ts.map +1 -0
- package/bin/src/commands/benchmark.js +135 -0
- package/bin/src/commands/benchmark.js.map +1 -0
- package/bin/src/commands/brief.d.ts.map +1 -0
- package/bin/src/commands/brief.js +37 -0
- package/bin/src/commands/brief.js.map +1 -0
- package/bin/src/commands/configCommand.d.ts.map +1 -0
- package/bin/src/commands/configCommand.js +53 -0
- package/bin/src/commands/configCommand.js.map +1 -0
- package/bin/src/commands/dashboard.d.ts.map +1 -0
- package/bin/src/commands/dashboard.js +41 -0
- package/bin/src/commands/dashboard.js.map +1 -0
- package/bin/src/commands/history.d.ts.map +1 -0
- package/bin/src/commands/history.js +20 -0
- package/bin/src/commands/history.js.map +1 -0
- package/bin/src/commands/init.d.ts.map +1 -1
- package/bin/src/commands/init.js +408 -79
- package/bin/src/commands/init.js.map +1 -1
- package/bin/src/commands/login.d.ts.map +1 -0
- package/bin/src/commands/login.js +190 -0
- package/bin/src/commands/login.js.map +1 -0
- package/bin/src/commands/logout.d.ts.map +1 -0
- package/bin/src/commands/logout.js +15 -0
- package/bin/src/commands/logout.js.map +1 -0
- package/bin/src/commands/mcp.d.ts.map +1 -0
- package/bin/src/commands/mcp.js +393 -0
- package/bin/src/commands/mcp.js.map +1 -0
- package/bin/src/commands/owners.js +6 -6
- package/bin/src/commands/owners.js.map +1 -1
- package/bin/src/commands/plan.d.ts.map +1 -0
- package/bin/src/commands/plan.js +152 -0
- package/bin/src/commands/plan.js.map +1 -0
- package/bin/src/commands/quota.d.ts.map +1 -0
- package/bin/src/commands/quota.js +32 -0
- package/bin/src/commands/quota.js.map +1 -0
- package/bin/src/commands/recommend.d.ts.map +1 -0
- package/bin/src/commands/recommend.js +46 -0
- package/bin/src/commands/recommend.js.map +1 -0
- package/bin/src/commands/risky.d.ts.map +1 -1
- package/bin/src/commands/risky.js +235 -39
- package/bin/src/commands/risky.js.map +1 -1
- package/bin/src/commands/setup.d.ts.map +1 -1
- package/bin/src/commands/setup.js +411 -16
- package/bin/src/commands/setup.js.map +1 -1
- package/bin/src/commands/signup.d.ts.map +1 -0
- package/bin/src/commands/signup.js +118 -0
- package/bin/src/commands/signup.js.map +1 -0
- package/bin/src/commands/undo.d.ts.map +1 -0
- package/bin/src/commands/undo.js +59 -0
- package/bin/src/commands/undo.js.map +1 -0
- package/bin/src/commands/web.d.ts.map +1 -0
- package/bin/src/commands/web.js +90 -0
- package/bin/src/commands/web.js.map +1 -0
- package/bin/src/discovery/config.d.ts.map +1 -0
- package/bin/src/discovery/config.js +41 -0
- package/bin/src/discovery/config.js.map +1 -0
- package/bin/src/discovery/display.d.ts.map +1 -0
- package/bin/src/discovery/display.js +57 -0
- package/bin/src/discovery/display.js.map +1 -0
- package/bin/src/discovery/messages.d.ts.map +1 -0
- package/bin/src/discovery/messages.js +7 -0
- package/bin/src/discovery/messages.js.map +1 -0
- package/bin/src/discovery/signals.d.ts.map +1 -0
- package/bin/src/discovery/signals.js +33 -0
- package/bin/src/discovery/signals.js.map +1 -0
- package/bin/src/engine/actionVerifier.d.ts.map +1 -0
- package/bin/src/engine/actionVerifier.js +283 -0
- package/bin/src/engine/actionVerifier.js.map +1 -0
- package/bin/src/engine/agentEngine.d.ts.map +1 -0
- package/bin/src/engine/agentEngine.js +2188 -0
- package/bin/src/engine/agentEngine.js.map +1 -0
- package/bin/src/engine/agentState.d.ts.map +1 -0
- package/bin/src/engine/agentState.js +381 -0
- package/bin/src/engine/agentState.js.map +1 -0
- package/bin/src/engine/browser/browserController.d.ts.map +1 -0
- package/bin/src/engine/browser/browserController.js +737 -0
- package/bin/src/engine/browser/browserController.js.map +1 -0
- package/bin/src/engine/browser/index.d.ts.map +1 -0
- package/bin/src/engine/browser/index.js +2 -0
- package/bin/src/engine/browser/index.js.map +1 -0
- package/bin/src/engine/computer/computerController.d.ts.map +1 -0
- package/bin/src/engine/computer/computerController.js +773 -0
- package/bin/src/engine/computer/computerController.js.map +1 -0
- package/bin/src/engine/computer/computerDiagnostic.d.ts.map +1 -0
- package/bin/src/engine/computer/computerDiagnostic.js +71 -0
- package/bin/src/engine/computer/computerDiagnostic.js.map +1 -0
- package/bin/src/engine/computer/index.d.ts.map +1 -0
- package/bin/src/engine/computer/index.js +4 -0
- package/bin/src/engine/computer/index.js.map +1 -0
- package/bin/src/engine/computer/mockComputer.d.ts.map +1 -0
- package/bin/src/engine/computer/mockComputer.js +260 -0
- package/bin/src/engine/computer/mockComputer.js.map +1 -0
- package/bin/src/engine/computer/scoutDriver.cs +1161 -0
- package/bin/src/engine/computer/scout_driver.exe +0 -0
- package/bin/src/engine/git.d.ts.map +1 -1
- package/bin/src/engine/git.js +71 -0
- package/bin/src/engine/git.js.map +1 -1
- package/bin/src/engine/llm.d.ts.map +1 -1
- package/bin/src/engine/llm.js +1264 -35
- package/bin/src/engine/llm.js.map +1 -1
- package/bin/src/engine/planner.d.ts.map +1 -0
- package/bin/src/engine/planner.js +321 -0
- package/bin/src/engine/planner.js.map +1 -0
- package/bin/src/engine/runtime/agentRuntime.d.ts.map +1 -0
- package/bin/src/engine/runtime/agentRuntime.js +1258 -0
- package/bin/src/engine/runtime/agentRuntime.js.map +1 -0
- package/bin/src/engine/runtime/index.d.ts.map +1 -0
- package/bin/src/engine/runtime/index.js +2 -0
- package/bin/src/engine/runtime/index.js.map +1 -0
- package/bin/src/engine/safety.d.ts.map +1 -0
- package/bin/src/engine/safety.js +316 -0
- package/bin/src/engine/safety.js.map +1 -0
- package/bin/src/engine/scoutAudioRecorder.cs +215 -0
- package/bin/src/engine/scout_audio_recorder.exe +0 -0
- package/bin/src/engine/toolRegistry.d.ts.map +1 -0
- package/bin/src/engine/toolRegistry.js +521 -0
- package/bin/src/engine/toolRegistry.js.map +1 -0
- package/bin/src/engine/verifier.d.ts.map +1 -0
- package/bin/src/engine/verifier.js +414 -0
- package/bin/src/engine/verifier.js.map +1 -0
- package/bin/src/engine/voiceEngine.d.ts.map +1 -0
- package/bin/src/engine/voiceEngine.js +474 -0
- package/bin/src/engine/voiceEngine.js.map +1 -0
- package/bin/src/engine/web.d.ts.map +1 -0
- package/bin/src/engine/web.js +161 -0
- package/bin/src/engine/web.js.map +1 -0
- package/bin/src/engine/workspace/agentWorkspace.d.ts.map +1 -0
- package/bin/src/engine/workspace/agentWorkspace.js +505 -0
- package/bin/src/engine/workspace/agentWorkspace.js.map +1 -0
- package/bin/src/engine/workspace/index.d.ts.map +1 -0
- package/bin/src/engine/workspace/index.js +2 -0
- package/bin/src/engine/workspace/index.js.map +1 -0
- package/bin/src/index.js +369 -19
- package/bin/src/index.js.map +1 -1
- package/bin/src/mcp/client.d.ts.map +1 -0
- package/bin/src/mcp/client.js +127 -0
- package/bin/src/mcp/client.js.map +1 -0
- package/bin/src/mcp/discovery.d.ts.map +1 -0
- package/bin/src/mcp/discovery.js +91 -0
- package/bin/src/mcp/discovery.js.map +1 -0
- package/bin/src/mcp/health.d.ts.map +1 -0
- package/bin/src/mcp/health.js +44 -0
- package/bin/src/mcp/health.js.map +1 -0
- package/bin/src/mcp/index.d.ts.map +1 -0
- package/bin/src/mcp/index.js +15 -0
- package/bin/src/mcp/index.js.map +1 -0
- package/bin/src/mcp/manager.d.ts.map +1 -0
- package/bin/src/mcp/manager.js +648 -0
- package/bin/src/mcp/manager.js.map +1 -0
- package/bin/src/mcp/permissions.d.ts.map +1 -0
- package/bin/src/mcp/permissions.js +50 -0
- package/bin/src/mcp/permissions.js.map +1 -0
- package/bin/src/mcp/persistence.d.ts.map +1 -0
- package/bin/src/mcp/persistence.js +171 -0
- package/bin/src/mcp/persistence.js.map +1 -0
- package/bin/src/mcp/recovery.d.ts.map +1 -0
- package/bin/src/mcp/recovery.js +75 -0
- package/bin/src/mcp/recovery.js.map +1 -0
- package/bin/src/mcp/telemetry.d.ts.map +1 -0
- package/bin/src/mcp/telemetry.js +82 -0
- package/bin/src/mcp/telemetry.js.map +1 -0
- package/bin/src/mcp/toolRouter.d.ts.map +1 -0
- package/bin/src/mcp/toolRouter.js +127 -0
- package/bin/src/mcp/toolRouter.js.map +1 -0
- package/bin/src/mcp/transport.d.ts.map +1 -0
- package/bin/src/mcp/transport.js +328 -0
- package/bin/src/mcp/transport.js.map +1 -0
- package/bin/src/mcp/types.d.ts.map +1 -0
- package/bin/src/mcp/types.js +6 -0
- package/bin/src/mcp/types.js.map +1 -0
- package/bin/src/server/server.d.ts.map +1 -0
- package/bin/src/server/server.js +989 -0
- package/bin/src/server/server.js.map +1 -0
- package/bin/src/utils/admin.d.ts.map +1 -0
- package/bin/src/utils/admin.js +231 -0
- package/bin/src/utils/admin.js.map +1 -0
- package/bin/src/utils/auditLogger.d.ts.map +1 -0
- package/bin/src/utils/auditLogger.js +70 -0
- package/bin/src/utils/auditLogger.js.map +1 -0
- package/bin/src/utils/auth.d.ts.map +1 -0
- package/bin/src/utils/auth.js +679 -0
- package/bin/src/utils/auth.js.map +1 -0
- package/bin/src/utils/branding.d.ts.map +1 -0
- package/bin/src/utils/branding.js +14 -0
- package/bin/src/utils/branding.js.map +1 -0
- package/bin/src/utils/config.d.ts.map +1 -1
- package/bin/src/utils/config.js +124 -3
- package/bin/src/utils/config.js.map +1 -1
- package/bin/src/utils/email.d.ts.map +1 -0
- package/bin/src/utils/email.js +267 -0
- package/bin/src/utils/email.js.map +1 -0
- package/bin/src/utils/firebaseAuth.d.ts.map +1 -0
- package/bin/src/utils/firebaseAuth.js +174 -0
- package/bin/src/utils/firebaseAuth.js.map +1 -0
- package/bin/src/utils/holoseneAuth.d.ts.map +1 -0
- package/bin/src/utils/holoseneAuth.js +307 -0
- package/bin/src/utils/holoseneAuth.js.map +1 -0
- package/bin/src/utils/language.d.ts.map +1 -0
- package/bin/src/utils/language.js +181 -0
- package/bin/src/utils/language.js.map +1 -0
- package/bin/src/utils/markdown.d.ts.map +1 -0
- package/bin/src/utils/markdown.js +215 -0
- package/bin/src/utils/markdown.js.map +1 -0
- package/bin/src/utils/payment.d.ts.map +1 -0
- package/bin/src/utils/payment.js +290 -0
- package/bin/src/utils/payment.js.map +1 -0
- package/bin/src/utils/pricing.d.ts.map +1 -0
- package/bin/src/utils/pricing.js +165 -0
- package/bin/src/utils/pricing.js.map +1 -0
- package/bin/src/utils/razorpay.d.ts.map +1 -0
- package/bin/src/utils/razorpay.js +144 -0
- package/bin/src/utils/razorpay.js.map +1 -0
- package/bin/src/utils/session.d.ts.map +1 -0
- package/bin/src/utils/session.js +82 -0
- package/bin/src/utils/session.js.map +1 -0
- package/bin/src/utils/updateCheck.d.ts.map +1 -0
- package/bin/src/utils/updateCheck.js +108 -0
- package/bin/src/utils/updateCheck.js.map +1 -0
- package/downloads/invoice-2026-03.pdf +1 -0
- package/firebase.json +74 -0
- package/package.json +49 -46
- package/web/ScoutFavicon.png +0 -0
- package/web/app.js +3944 -0
- package/web/styles.css +3081 -0
- package/.ft/context.json +0 -17
- package/email-template.txt +0 -44
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
import chalk from 'chalk';
|
|
2
|
+
import { loadHistory } from '../utils/config.js';
|
|
3
|
+
import { safeNote } from '../utils/markdown.js';
|
|
4
|
+
export async function handleHistory() {
|
|
5
|
+
const history = loadHistory();
|
|
6
|
+
if (history.length === 0) {
|
|
7
|
+
safeNote(chalk.yellow('No recorded FT actions found in this repository yet.\nCommands like `ft fix` and configuration updates will log history entries here.'), ' FrontTerrain Execution History');
|
|
8
|
+
return;
|
|
9
|
+
}
|
|
10
|
+
const logLines = history.map((item, idx) => {
|
|
11
|
+
const timeStr = new Date(item.timestamp).toLocaleString();
|
|
12
|
+
return (`${chalk.bold.cyan(`#${history.length - idx}`)} ${chalk.bold.green(item.command)} ${chalk.dim(`(${timeStr})`)}\n` +
|
|
13
|
+
` • ${item.description}\n` +
|
|
14
|
+
(item.branchName ? ` • Branch: ${chalk.yellow(item.branchName)}\n` : '') +
|
|
15
|
+
(item.affectedFiles && item.affectedFiles.length > 0 ? ` • Files: ${chalk.dim(item.affectedFiles.join(', '))}` : ''));
|
|
16
|
+
});
|
|
17
|
+
safeNote(logLines.join('\n\n') +
|
|
18
|
+
`\n\n${chalk.dim('Run `ft undo` to revert the last action performed by Scout.')}`, ' FrontTerrain Execution History');
|
|
19
|
+
}
|
|
20
|
+
//# sourceMappingURL=history.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"history.js","sourceRoot":"","sources":["history.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,MAAM,OAAO,CAAC;AAC1B,OAAO,EAAE,WAAW,EAAE,MAAM,oBAAoB,CAAC;AACjD,OAAO,EAAE,QAAQ,EAAE,MAAM,sBAAsB,CAAC;AAEhD,MAAM,CAAC,KAAK,UAAU,aAAa;IAC/B,MAAM,OAAO,GAAG,WAAW,EAAE,CAAC;IAE9B,IAAI,OAAO,CAAC,MAAM,KAAK,CAAC,EAAE,CAAC;QACvB,QAAQ,CACJ,KAAK,CAAC,MAAM,CAAC,uIAAuI,CAAC,EACrJ,iCAAiC,CACpC,CAAC;QACF,OAAO;IACX,CAAC;IAED,MAAM,QAAQ,GAAG,OAAO,CAAC,GAAG,CAAC,CAAC,IAAI,EAAE,GAAG,EAAE,EAAE;QACvC,MAAM,OAAO,GAAG,IAAI,IAAI,CAAC,IAAI,CAAC,SAAS,CAAC,CAAC,cAAc,EAAE,CAAC;QAC1D,OAAO,CACH,GAAG,KAAK,CAAC,IAAI,CAAC,IAAI,CAAC,IAAI,OAAO,CAAC,MAAM,GAAG,GAAG,EAAE,CAAC,IAAI,KAAK,CAAC,IAAI,CAAC,KAAK,CAAC,IAAI,CAAC,OAAO,CAAC,IAAI,KAAK,CAAC,GAAG,CAAC,IAAI,OAAO,GAAG,CAAC,IAAI;YACjH,MAAM,IAAI,CAAC,WAAW,IAAI;YAC1B,CAAC,IAAI,CAAC,UAAU,CAAC,CAAC,CAAC,cAAc,KAAK,CAAC,MAAM,CAAC,IAAI,CAAC,UAAU,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,CAAC;YACxE,CAAC,IAAI,CAAC,aAAa,IAAI,IAAI,CAAC,aAAa,CAAC,MAAM,GAAG,CAAC,CAAC,CAAC,CAAC,aAAa,KAAK,CAAC,GAAG,CAAC,IAAI,CAAC,aAAa,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC,CACvH,CAAC;IACN,CAAC,CAAC,CAAC;IAEH,QAAQ,CACJ,QAAQ,CAAC,IAAI,CAAC,MAAM,CAAC;QACrB,OAAO,KAAK,CAAC,GAAG,CAAC,6DAA6D,CAAC,EAAE,EACjF,iCAAiC,CACpC,CAAC;AACN,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":"AAuOA,wBAAsB,UAAU,CAAC,OAAO,CAAC,EAAE,MAAM,EAAE,OAAO,CAAC,EAAE;IAAE,GAAG,CAAC,EAAE,OAAO,CAAC;IAAC,GAAG,CAAC,EAAE,OAAO,CAAC;IAAC,IAAI,CAAC,EAAE,MAAM,CAAC;IAAC,MAAM,CAAC,EAAE,OAAO,CAAC;IAAC,QAAQ,CAAC,EAAE,OAAO,CAAA;CAAE,iBA+ZjJ"}
|
package/bin/src/commands/init.js
CHANGED
|
@@ -1,11 +1,99 @@
|
|
|
1
1
|
import fs from 'fs';
|
|
2
2
|
import path from 'path';
|
|
3
3
|
import os from 'os';
|
|
4
|
-
import { spinner, note, password, isCancel } from '@clack/prompts';
|
|
4
|
+
import { spinner, note, password, isCancel, text, select, confirm } from '@clack/prompts';
|
|
5
5
|
import chalk from 'chalk';
|
|
6
6
|
import { simpleGit } from 'simple-git';
|
|
7
|
-
import { generateRepoSummary } from '../engine/llm.js';
|
|
8
|
-
import { saveContext } from '../utils/config.js';
|
|
7
|
+
import { generateRepoSummary, askRepoQuestion } from '../engine/llm.js';
|
|
8
|
+
import { saveContext, loadContext, saveScanReport } from '../utils/config.js';
|
|
9
|
+
import { renderMarkdown, formatInlineMarkdown, safeNote } from '../utils/markdown.js';
|
|
10
|
+
import { normalizeLanguage, getLanguageInstruction } from '../utils/language.js';
|
|
11
|
+
function detectPackageManager(dir) {
|
|
12
|
+
if (fs.existsSync(path.join(dir, 'pnpm-lock.yaml')))
|
|
13
|
+
return 'pnpm';
|
|
14
|
+
if (fs.existsSync(path.join(dir, 'yarn.lock')))
|
|
15
|
+
return 'yarn';
|
|
16
|
+
if (fs.existsSync(path.join(dir, 'bun.lockb')) || fs.existsSync(path.join(dir, 'bun.lock')))
|
|
17
|
+
return 'bun';
|
|
18
|
+
if (fs.existsSync(path.join(dir, 'package-lock.json')))
|
|
19
|
+
return 'npm';
|
|
20
|
+
if (fs.existsSync(path.join(dir, 'Cargo.lock')))
|
|
21
|
+
return 'cargo';
|
|
22
|
+
if (fs.existsSync(path.join(dir, 'poetry.lock')))
|
|
23
|
+
return 'poetry';
|
|
24
|
+
if (fs.existsSync(path.join(dir, 'Pipfile')))
|
|
25
|
+
return 'pipenv';
|
|
26
|
+
if (fs.existsSync(path.join(dir, 'build.gradle')) || fs.existsSync(path.join(dir, 'gradlew')))
|
|
27
|
+
return 'gradle';
|
|
28
|
+
return 'npm';
|
|
29
|
+
}
|
|
30
|
+
function detectWorkspaces(dir) {
|
|
31
|
+
const workspaces = [];
|
|
32
|
+
const checks = [
|
|
33
|
+
{ file: 'pnpm-workspace.yaml', type: 'pnpm Workspaces' },
|
|
34
|
+
{ file: 'turbo.json', type: 'Turborepo' },
|
|
35
|
+
{ file: 'lerna.json', type: 'Lerna Monorepo' },
|
|
36
|
+
{ file: 'nx.json', type: 'Nx Workspace' },
|
|
37
|
+
];
|
|
38
|
+
for (const c of checks) {
|
|
39
|
+
if (fs.existsSync(path.join(dir, c.file))) {
|
|
40
|
+
workspaces.push(c.type);
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
const pkgJsonPath = path.join(dir, 'package.json');
|
|
44
|
+
if (fs.existsSync(pkgJsonPath)) {
|
|
45
|
+
try {
|
|
46
|
+
const pkg = JSON.parse(fs.readFileSync(pkgJsonPath, 'utf-8'));
|
|
47
|
+
if (pkg.workspaces) {
|
|
48
|
+
if (Array.isArray(pkg.workspaces)) {
|
|
49
|
+
pkg.workspaces.forEach((w) => workspaces.push(`package (${w})`));
|
|
50
|
+
}
|
|
51
|
+
else if (pkg.workspaces.packages && Array.isArray(pkg.workspaces.packages)) {
|
|
52
|
+
pkg.workspaces.packages.forEach((w) => workspaces.push(`package (${w})`));
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
catch { }
|
|
57
|
+
}
|
|
58
|
+
// Check common subdirectories
|
|
59
|
+
const subDirs = ['apps', 'packages', 'services', 'modules', 'libs'];
|
|
60
|
+
for (const sub of subDirs) {
|
|
61
|
+
const subPath = path.join(dir, sub);
|
|
62
|
+
if (fs.existsSync(subPath) && fs.statSync(subPath).isDirectory()) {
|
|
63
|
+
try {
|
|
64
|
+
const contents = fs.readdirSync(subPath).filter((f) => {
|
|
65
|
+
const p = path.join(subPath, f);
|
|
66
|
+
return fs.statSync(p).isDirectory() && !f.startsWith('.');
|
|
67
|
+
});
|
|
68
|
+
if (contents.length > 0) {
|
|
69
|
+
workspaces.push(`${sub}/ (${contents.length} packages: ${contents.slice(0, 3).join(', ')}${contents.length > 3 ? '...' : ''})`);
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
catch { }
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
const isMonorepo = workspaces.length > 0;
|
|
76
|
+
return { isMonorepo, workspaces: Array.from(new Set(workspaces)) };
|
|
77
|
+
}
|
|
78
|
+
async function bootstrapEnvironmentFile(dir) {
|
|
79
|
+
const envPath = path.join(dir, '.env');
|
|
80
|
+
if (fs.existsSync(envPath))
|
|
81
|
+
return false;
|
|
82
|
+
const exampleCandidates = ['.env.example', '.env.template', '.env.local.example', '.env.sample', 'env.example'];
|
|
83
|
+
for (const candidate of exampleCandidates) {
|
|
84
|
+
const examplePath = path.join(dir, candidate);
|
|
85
|
+
if (fs.existsSync(examplePath)) {
|
|
86
|
+
try {
|
|
87
|
+
const content = fs.readFileSync(examplePath, 'utf-8');
|
|
88
|
+
fs.writeFileSync(envPath, content);
|
|
89
|
+
note(chalk.green(`Auto-created ${chalk.bold('.env')} from ${chalk.bold(candidate)}.\nPlease update any API keys or secrets in .env before running services.`), ' Environment Auto-Setup');
|
|
90
|
+
return true;
|
|
91
|
+
}
|
|
92
|
+
catch { }
|
|
93
|
+
}
|
|
94
|
+
}
|
|
95
|
+
return false;
|
|
96
|
+
}
|
|
9
97
|
function extractNpmPackageName(target) {
|
|
10
98
|
if (target.startsWith('npm:')) {
|
|
11
99
|
return target.slice(4).trim();
|
|
@@ -18,7 +106,8 @@ function extractNpmPackageName(target) {
|
|
|
18
106
|
/^git@/i.test(target) ||
|
|
19
107
|
target.includes('github.com') ||
|
|
20
108
|
target.includes('gitlab.com') ||
|
|
21
|
-
target.endsWith('.git')
|
|
109
|
+
target.endsWith('.git') ||
|
|
110
|
+
(/^[a-zA-Z0-9_-]+\/[a-zA-Z0-9_.-]+$/.test(target) && !target.startsWith('@'));
|
|
22
111
|
if (!isGitOrFile) {
|
|
23
112
|
if (/^(@[a-z0-9-~][a-z0-9-._~]*\/)?[a-z0-9-~][a-z0-9-._~]*$/i.test(target)) {
|
|
24
113
|
return target;
|
|
@@ -59,8 +148,9 @@ async function getNpmRepoUrl(pkgName) {
|
|
|
59
148
|
}
|
|
60
149
|
}
|
|
61
150
|
function cleanGitHubUrl(targetUrl) {
|
|
62
|
-
|
|
63
|
-
|
|
151
|
+
const trimmed = targetUrl.trim();
|
|
152
|
+
if (trimmed.includes('github.com')) {
|
|
153
|
+
const match = trimmed.match(/github\.com\/([^/]+(?:\/[^/]+)+)/i);
|
|
64
154
|
if (match && match[1]) {
|
|
65
155
|
let parts = match[1].split('/').filter(Boolean);
|
|
66
156
|
const branchIndex = parts.findIndex((p) => ['tree', 'blob', 'src'].includes(p.toLowerCase()));
|
|
@@ -75,11 +165,67 @@ function cleanGitHubUrl(targetUrl) {
|
|
|
75
165
|
}
|
|
76
166
|
}
|
|
77
167
|
}
|
|
78
|
-
|
|
168
|
+
// Short format: owner/repo (e.g. facebook/react)
|
|
169
|
+
if (/^[a-zA-Z0-9_-]+\/[a-zA-Z0-9_.-]+$/.test(trimmed) && !trimmed.startsWith('@')) {
|
|
170
|
+
const parts = trimmed.split('/');
|
|
171
|
+
if (parts.length === 2 && parts[0] && parts[1]) {
|
|
172
|
+
const owner = parts[0];
|
|
173
|
+
const repo = parts[1].replace(/\.git$/i, '');
|
|
174
|
+
return `https://github.com/${owner}/${repo}.git`;
|
|
175
|
+
}
|
|
176
|
+
}
|
|
177
|
+
return trimmed;
|
|
79
178
|
}
|
|
80
|
-
|
|
179
|
+
async function isPublicRepo(gitUrl) {
|
|
180
|
+
if (!gitUrl.startsWith('https://') && !gitUrl.startsWith('http://')) {
|
|
181
|
+
return false;
|
|
182
|
+
}
|
|
183
|
+
try {
|
|
184
|
+
const cleanUrl = gitUrl.replace(/\.git$/i, '');
|
|
185
|
+
const smartHttpEndpoint = `${cleanUrl}.git/info/refs?service=git-upload-pack`;
|
|
186
|
+
const res = await fetch(smartHttpEndpoint, {
|
|
187
|
+
method: 'GET',
|
|
188
|
+
headers: {
|
|
189
|
+
'User-Agent': 'git/2.39.0',
|
|
190
|
+
},
|
|
191
|
+
signal: AbortSignal.timeout(5000),
|
|
192
|
+
});
|
|
193
|
+
if (res.status === 200) {
|
|
194
|
+
return true;
|
|
195
|
+
}
|
|
196
|
+
}
|
|
197
|
+
catch { }
|
|
198
|
+
if (gitUrl.includes('github.com')) {
|
|
199
|
+
try {
|
|
200
|
+
const match = gitUrl.match(/github\.com\/([^/]+)\/([^/]+)/i);
|
|
201
|
+
if (match && match[1] && match[2]) {
|
|
202
|
+
const owner = match[1];
|
|
203
|
+
const repo = match[2].replace(/\.git$/i, '');
|
|
204
|
+
const apiRes = await fetch(`https://api.github.com/repos/${encodeURIComponent(owner)}/${encodeURIComponent(repo)}`, {
|
|
205
|
+
headers: {
|
|
206
|
+
'User-Agent': 'FrontTerrain-CLI',
|
|
207
|
+
'Accept': 'application/vnd.github.v3+json',
|
|
208
|
+
},
|
|
209
|
+
signal: AbortSignal.timeout(5000),
|
|
210
|
+
});
|
|
211
|
+
if (apiRes.ok) {
|
|
212
|
+
const data = (await apiRes.json());
|
|
213
|
+
if (data && data.private === false) {
|
|
214
|
+
return true;
|
|
215
|
+
}
|
|
216
|
+
}
|
|
217
|
+
}
|
|
218
|
+
}
|
|
219
|
+
catch { }
|
|
220
|
+
}
|
|
221
|
+
return false;
|
|
222
|
+
}
|
|
223
|
+
export async function handleInit(repoUrl, options) {
|
|
81
224
|
const s = spinner();
|
|
82
225
|
let targetDir = process.cwd();
|
|
226
|
+
const existingContext = loadContext();
|
|
227
|
+
const targetLang = normalizeLanguage(options?.lang) || normalizeLanguage(existingContext?.language);
|
|
228
|
+
const langDisplay = targetLang ? getLanguageInstruction(targetLang, options?.native).displayName : undefined;
|
|
83
229
|
if (repoUrl && repoUrl.trim()) {
|
|
84
230
|
const rawTarget = repoUrl.trim();
|
|
85
231
|
const resolvedPath = path.resolve(rawTarget);
|
|
@@ -93,98 +239,191 @@ export async function handleInit(repoUrl) {
|
|
|
93
239
|
/^git@/i.test(rawTarget) ||
|
|
94
240
|
rawTarget.includes('github.com') ||
|
|
95
241
|
rawTarget.includes('gitlab.com') ||
|
|
96
|
-
rawTarget.endsWith('.git')
|
|
242
|
+
rawTarget.endsWith('.git') ||
|
|
243
|
+
(/^[a-zA-Z0-9_-]+\/[a-zA-Z0-9_.-]+$/.test(rawTarget) && !rawTarget.startsWith('@'));
|
|
97
244
|
if (npmPkgName && !isGitUrl) {
|
|
98
245
|
s.start(`Resolving npm package repository (${chalk.cyan(npmPkgName)})...`);
|
|
99
246
|
const resolvedUrl = await getNpmRepoUrl(npmPkgName);
|
|
100
247
|
if (!resolvedUrl) {
|
|
101
248
|
s.stop(chalk.red(`Failed to resolve npm package repository.`));
|
|
102
|
-
note(chalk.red(`The specified repository is private or does not exist.`), '
|
|
249
|
+
note(chalk.red(`The specified repository is private or does not exist.`), ' Invalid Target');
|
|
103
250
|
return;
|
|
104
251
|
}
|
|
105
252
|
gitTargetUrl = cleanGitHubUrl(resolvedUrl);
|
|
106
253
|
s.stop(`Resolved npm package to repository ${chalk.cyan(gitTargetUrl)}.`);
|
|
107
254
|
}
|
|
108
|
-
s.start(`Cloning remote repository (${chalk.cyan(gitTargetUrl)})...`);
|
|
109
255
|
const repoName = path.basename(gitTargetUrl, '.git') || 'cloned-repo';
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
256
|
+
if (process.stdout.isTTY) {
|
|
257
|
+
const cloneChoice = await select({
|
|
258
|
+
message: chalk.bold(`Where would you like to clone ${chalk.cyan(repoName)}?`),
|
|
259
|
+
options: [
|
|
260
|
+
{ value: 'root', label: ' Root Repo (Current Directory)', hint: `Clone inside current directory: ./${repoName}` },
|
|
261
|
+
{ value: 'temp', label: ' Temp (Temporary Folder)', hint: 'Clone to OS temporary directory (isolated sandbox)' },
|
|
262
|
+
{ value: 'custom', label: '️ Input Yourself (Custom Path)', hint: 'Specify your own custom folder destination' },
|
|
263
|
+
],
|
|
264
|
+
});
|
|
265
|
+
if (isCancel(cloneChoice)) {
|
|
266
|
+
note(chalk.yellow('Clone operation cancelled.'), ' Init Cancelled');
|
|
267
|
+
return;
|
|
268
|
+
}
|
|
269
|
+
if (cloneChoice === 'root') {
|
|
270
|
+
targetDir = path.resolve(process.cwd(), repoName);
|
|
271
|
+
}
|
|
272
|
+
else if (cloneChoice === 'custom') {
|
|
273
|
+
const customPath = await text({
|
|
274
|
+
message: 'Enter custom folder path to clone into:',
|
|
275
|
+
placeholder: `./${repoName} or C:\\projects\\${repoName}`,
|
|
276
|
+
validate: (val) => (!val || !val.trim() ? 'Path cannot be empty' : undefined),
|
|
277
|
+
});
|
|
278
|
+
if (isCancel(customPath)) {
|
|
279
|
+
note(chalk.yellow('Clone operation cancelled.'), ' Init Cancelled');
|
|
280
|
+
return;
|
|
281
|
+
}
|
|
282
|
+
targetDir = path.resolve(process.cwd(), customPath.trim());
|
|
283
|
+
}
|
|
284
|
+
else {
|
|
285
|
+
const tempParent = path.join(os.tmpdir(), 'scout-repos');
|
|
286
|
+
targetDir = path.join(tempParent, `${repoName}-${Date.now()}`);
|
|
287
|
+
}
|
|
114
288
|
}
|
|
289
|
+
else {
|
|
290
|
+
const tempParent = path.join(os.tmpdir(), 'scout-repos');
|
|
291
|
+
targetDir = path.join(tempParent, `${repoName}-${Date.now()}`);
|
|
292
|
+
}
|
|
293
|
+
if (!fs.existsSync(path.dirname(targetDir))) {
|
|
294
|
+
fs.mkdirSync(path.dirname(targetDir), { recursive: true });
|
|
295
|
+
}
|
|
296
|
+
s.start(`Cloning remote repository (${chalk.cyan(gitTargetUrl)}) to ${chalk.cyan(targetDir)}...`);
|
|
115
297
|
const git = simpleGit();
|
|
116
298
|
try {
|
|
117
299
|
await git.clone(gitTargetUrl, targetDir, ['--depth', '1']);
|
|
118
300
|
s.stop(`Remote repository cloned successfully.`);
|
|
119
301
|
}
|
|
120
302
|
catch (err) {
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
303
|
+
const isPublic = await isPublicRepo(gitTargetUrl);
|
|
304
|
+
if (isPublic) {
|
|
305
|
+
// Retry without depth if initial clone failed for a public repo
|
|
306
|
+
try {
|
|
307
|
+
if (fs.existsSync(targetDir)) {
|
|
308
|
+
fs.rmSync(targetDir, { recursive: true, force: true });
|
|
309
|
+
}
|
|
310
|
+
await git.clone(gitTargetUrl, targetDir);
|
|
311
|
+
s.stop(`Remote repository cloned successfully.`);
|
|
312
|
+
}
|
|
313
|
+
catch (retryErr) {
|
|
314
|
+
s.stop(chalk.red(`Failed to clone public repository.`));
|
|
315
|
+
note(chalk.red(`Could not clone public repository (${gitTargetUrl}).\n` +
|
|
316
|
+
`Git Error: ${retryErr?.message || err?.message || 'Unknown git clone error'}\n\n` +
|
|
317
|
+
`Please check your network connection or verify the repository URL.`), ' Clone Failed');
|
|
318
|
+
return;
|
|
319
|
+
}
|
|
320
|
+
}
|
|
321
|
+
else {
|
|
322
|
+
s.stop(chalk.yellow(`Initial clone failed (repository may be private or require authentication).`));
|
|
323
|
+
// Interactive login / token prompt for private repos ONLY
|
|
324
|
+
if (gitTargetUrl.startsWith('https://')) {
|
|
325
|
+
const tokenInput = await password({
|
|
326
|
+
message: 'Enter your GitHub / GitLab Personal Access Token (PAT) to access this private repository:',
|
|
327
|
+
mask: '*',
|
|
328
|
+
validate: (val) => (!val || !val.trim() ? 'Token cannot be empty' : undefined),
|
|
329
|
+
});
|
|
330
|
+
if (!isCancel(tokenInput) && typeof tokenInput === 'string' && tokenInput.trim()) {
|
|
331
|
+
const cleanToken = tokenInput.trim();
|
|
332
|
+
const encodedToken = encodeURIComponent(cleanToken);
|
|
333
|
+
// Inject encoded token into URL: https://<token>@github.com/...
|
|
334
|
+
const authenticatedUrl = gitTargetUrl.replace(/^https:\/\//i, `https://${encodedToken}@`);
|
|
335
|
+
const retrySpinner = spinner();
|
|
336
|
+
retrySpinner.start(`Retrying clone with provided token...`);
|
|
337
|
+
try {
|
|
338
|
+
if (fs.existsSync(targetDir)) {
|
|
339
|
+
fs.rmSync(targetDir, { recursive: true, force: true });
|
|
340
|
+
}
|
|
341
|
+
await git.clone(authenticatedUrl, targetDir, ['--depth', '1']);
|
|
342
|
+
retrySpinner.stop(chalk.green(`Remote repository cloned successfully!`));
|
|
343
|
+
}
|
|
344
|
+
catch (retryErr) {
|
|
345
|
+
retrySpinner.stop(chalk.red(`Authentication failed with the provided token.`));
|
|
346
|
+
note(chalk.red(`Could not clone repository with the provided token.\n` +
|
|
347
|
+
`Error details: ${retryErr?.message || 'Authentication error'}\n\n` +
|
|
348
|
+
`Alternative options:\n` +
|
|
349
|
+
` • Use SSH: ${chalk.cyan('scout init git@github.com:owner/repo.git')}\n` +
|
|
350
|
+
` • Or clone locally first: ${chalk.cyan('scout init ./my-repo-folder')}`), ' Authentication Failed');
|
|
351
|
+
return;
|
|
138
352
|
}
|
|
139
|
-
await git.clone(authenticatedUrl, targetDir, ['--depth', '1']);
|
|
140
|
-
retrySpinner.stop(chalk.green(`Remote repository cloned successfully!`));
|
|
141
353
|
}
|
|
142
|
-
|
|
143
|
-
|
|
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-scout init git@github.com:owner/repo.git')}\n` +
|
|
148
|
-
` • Or clone locally first: ${chalk.cyan('ft-scout init ./my-repo-folder')}`), '❌ Authentication Failed');
|
|
354
|
+
else {
|
|
355
|
+
note(chalk.yellow('Login cancelled. Repository initialization aborted.'), ' Clone Cancelled');
|
|
149
356
|
return;
|
|
150
357
|
}
|
|
151
358
|
}
|
|
152
359
|
else {
|
|
153
|
-
note(chalk.
|
|
360
|
+
note(chalk.red(`The specified repository is private or does not exist.\n\n` +
|
|
361
|
+
chalk.bold(' How to access private repositories:') + '\n' +
|
|
362
|
+
` • Use SSH URL: ${chalk.cyan('scout init git@github.com:owner/repo.git')}\n` +
|
|
363
|
+
` • Or use PAT Token URL: ${chalk.cyan('scout init https://<TOKEN>@github.com/owner/repo.git')}\n` +
|
|
364
|
+
` • Or clone locally first and run: ${chalk.cyan('scout init ./my-repo-folder')}`), ' Clone Error');
|
|
154
365
|
return;
|
|
155
366
|
}
|
|
156
367
|
}
|
|
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-scout init git@github.com:owner/repo.git')}\n` +
|
|
161
|
-
` • Or use PAT Token URL: ${chalk.cyan('ft-scout 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
368
|
}
|
|
166
369
|
}
|
|
167
370
|
}
|
|
168
|
-
|
|
371
|
+
const pkgManager = detectPackageManager(targetDir);
|
|
372
|
+
const workspaceInfo = detectWorkspaces(targetDir);
|
|
373
|
+
await bootstrapEnvironmentFile(targetDir);
|
|
374
|
+
s.start(`Analyzing repository at ${chalk.cyan(targetDir)}${langDisplay ? ` (Language: ${chalk.bold.yellow(langDisplay)})` : ''}...`);
|
|
375
|
+
// Perform quick pre-flight fragility scan for .ft/scan-report.json
|
|
376
|
+
try {
|
|
377
|
+
const git = simpleGit(targetDir);
|
|
378
|
+
const rawLog = await git.raw(['log', '-n', '100', '--name-only', '--format=COMMIT:%H %s']);
|
|
379
|
+
const fileStats = {};
|
|
380
|
+
const logLines = rawLog.split('\n');
|
|
381
|
+
let currentIsHotfix = false;
|
|
382
|
+
for (const rawLine of logLines) {
|
|
383
|
+
const line = rawLine.trim();
|
|
384
|
+
if (!line)
|
|
385
|
+
continue;
|
|
386
|
+
if (line.startsWith('COMMIT:')) {
|
|
387
|
+
currentIsHotfix = /fix|bug|hotfix|patch/i.test(line);
|
|
388
|
+
}
|
|
389
|
+
else if (currentIsHotfix) {
|
|
390
|
+
fileStats[line] = (fileStats[line] || 0) + 1;
|
|
391
|
+
}
|
|
392
|
+
}
|
|
393
|
+
const scanReport = Object.entries(fileStats).map(([file, hotfixes]) => ({
|
|
394
|
+
file,
|
|
395
|
+
hotfixes,
|
|
396
|
+
riskLevel: hotfixes >= 3 ? 'HIGH' : hotfixes >= 1 ? 'MEDIUM' : 'LOW',
|
|
397
|
+
}));
|
|
398
|
+
if (scanReport.length > 0) {
|
|
399
|
+
saveScanReport(scanReport);
|
|
400
|
+
}
|
|
401
|
+
}
|
|
402
|
+
catch { }
|
|
169
403
|
// Scan file tree up to 2 levels deep
|
|
170
|
-
const
|
|
404
|
+
const ignoredDirs = new Set([
|
|
405
|
+
'node_modules', '.git', 'dist', 'build', '.dart_tool', '.next', 'out', 'vendor',
|
|
406
|
+
'target', 'bin', 'obj', 'coverage', '.ft', '.vscode', '.idea', 'tmp', 'temp',
|
|
407
|
+
'venv', '.venv', '__pycache__', '.turbo', '.cache'
|
|
408
|
+
]);
|
|
409
|
+
const getFiles = (dir, currentDepth = 2) => {
|
|
171
410
|
let results = [];
|
|
172
411
|
try {
|
|
173
412
|
const list = fs.readdirSync(dir);
|
|
174
413
|
for (const file of list) {
|
|
175
|
-
if (
|
|
414
|
+
if (ignoredDirs.has(file) || (file.startsWith('.') && file !== '.github'))
|
|
176
415
|
continue;
|
|
177
416
|
const fullPath = path.join(dir, file);
|
|
178
|
-
const relPath = path.relative(
|
|
417
|
+
const relPath = path.relative(targetDir, fullPath);
|
|
179
418
|
const stat = fs.statSync(fullPath);
|
|
180
419
|
if (stat.isDirectory()) {
|
|
181
|
-
results.push(relPath + '/');
|
|
182
|
-
if (
|
|
183
|
-
results = results.concat(getFiles(fullPath,
|
|
420
|
+
results.push(relPath.replace(/\\/g, '/') + '/');
|
|
421
|
+
if (currentDepth > 1) {
|
|
422
|
+
results = results.concat(getFiles(fullPath, currentDepth - 1));
|
|
184
423
|
}
|
|
185
424
|
}
|
|
186
425
|
else {
|
|
187
|
-
results.push(relPath);
|
|
426
|
+
results.push(relPath.replace(/\\/g, '/'));
|
|
188
427
|
}
|
|
189
428
|
}
|
|
190
429
|
}
|
|
@@ -208,33 +447,123 @@ export async function handleInit(repoUrl) {
|
|
|
208
447
|
projectName = path.basename(rawTarget, '.git');
|
|
209
448
|
}
|
|
210
449
|
}
|
|
211
|
-
const aiInsight = await generateRepoSummary(projectName, filesList, manifestContent || '{}');
|
|
212
|
-
// Save context into local .ft folder
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
450
|
+
const aiInsight = await generateRepoSummary(projectName, filesList, manifestContent || '{}', targetLang, options?.native);
|
|
451
|
+
// Save context into local .ft folder (only if not skipSave)
|
|
452
|
+
if (!options?.skipSave) {
|
|
453
|
+
saveContext({
|
|
454
|
+
projectName,
|
|
455
|
+
repoUrl: repoUrl?.trim() || undefined,
|
|
456
|
+
targetDir,
|
|
457
|
+
stack: aiInsight.summary,
|
|
458
|
+
entryPoint: aiInsight.entryPoint,
|
|
459
|
+
painPoints: aiInsight.painPoints,
|
|
460
|
+
readOrder: aiInsight.readOrder,
|
|
461
|
+
createdAt: new Date().toISOString(),
|
|
462
|
+
language: targetLang,
|
|
463
|
+
packageManager: pkgManager,
|
|
464
|
+
isMonorepo: workspaceInfo.isMonorepo,
|
|
465
|
+
workspaces: workspaceInfo.workspaces,
|
|
466
|
+
architectureDiagram: aiInsight.architectureDiagram,
|
|
467
|
+
});
|
|
468
|
+
}
|
|
223
469
|
s.stop('Repository analysis complete.');
|
|
470
|
+
const cleanedPainPoints = (aiInsight.painPoints || [])
|
|
471
|
+
.map((p) => p.trim())
|
|
472
|
+
.filter((p) => p.length > 5 && !/^[,\s.\-[\]:]+$/.test(p));
|
|
473
|
+
const cleanedReadOrder = (aiInsight.readOrder || [])
|
|
474
|
+
.map((r) => r.trim())
|
|
475
|
+
.filter((r) => r.length > 5 && !/^[,\s.\-[\]:]+$/.test(r));
|
|
476
|
+
const painPointsToDisplay = cleanedPainPoints.length > 0 ? cleanedPainPoints : [
|
|
477
|
+
' [Subsystem Dependency]: Verify API keys or external proxy authentication service connectivity.',
|
|
478
|
+
' [Environment Requirements]: Ensure runtime dependencies match system execution requirements.'
|
|
479
|
+
];
|
|
480
|
+
const readOrderToDisplay = cleanedReadOrder.length > 0 ? cleanedReadOrder : [
|
|
481
|
+
'package.json -> Core manifest defining build targets and dependency graph',
|
|
482
|
+
'src/index.ts -> Primary router establishing CLI hooks and command workflow'
|
|
483
|
+
];
|
|
484
|
+
// Format workspace & package manager details
|
|
485
|
+
const workspaceStr = workspaceInfo.isMonorepo
|
|
486
|
+
? `\nWorkspace Type: ${chalk.bold.magenta('Monorepo')} (${workspaceInfo.workspaces.join(', ')})`
|
|
487
|
+
: '';
|
|
488
|
+
const archDiagramStr = aiInsight.architectureDiagram
|
|
489
|
+
? `\n\n${chalk.bold.blue('️ Subsystem Architecture Flow:')}\n${chalk.dim(aiInsight.architectureDiagram)}`
|
|
490
|
+
: '';
|
|
224
491
|
// Print repository summary
|
|
225
|
-
|
|
226
|
-
Location: ${chalk.dim(targetDir)}
|
|
492
|
+
safeNote(`Project Name: ${chalk.bold.green(projectName)}
|
|
493
|
+
Location: ${chalk.dim(targetDir)}${langDisplay ? `\nLanguage: ${chalk.bold.yellow(langDisplay)}` : ''}
|
|
494
|
+
Package Manager: ${chalk.bold.cyan(pkgManager.toUpperCase())}${workspaceStr}
|
|
227
495
|
|
|
228
|
-
${chalk.bold.magenta('
|
|
229
|
-
${
|
|
496
|
+
${chalk.bold.magenta(' Architectural Executive Summary & Stack:')}
|
|
497
|
+
${renderMarkdown(aiInsight.summary)}${archDiagramStr}
|
|
230
498
|
|
|
231
|
-
${chalk.bold.cyan('
|
|
499
|
+
${chalk.bold.cyan(' Main Entry Point:')}
|
|
232
500
|
${chalk.cyan(aiInsight.entryPoint)}
|
|
233
501
|
|
|
234
|
-
${chalk.bold.red('
|
|
235
|
-
${
|
|
502
|
+
${chalk.bold.red(' Known Architectural Risks & Hotspots:')}
|
|
503
|
+
${painPointsToDisplay.map((p) => ` • ${formatInlineMarkdown(p)}`).join('\n\n')}
|
|
236
504
|
|
|
237
|
-
${chalk.bold.green('
|
|
238
|
-
${
|
|
505
|
+
${chalk.bold.green(' Strategic Onboarding Read Order:')}
|
|
506
|
+
${readOrderToDisplay.map((r, idx) => ` ${idx + 1}. ${formatInlineMarkdown(r)}`).join('\n')}`, ' FrontTerrain Codebase Teardown.');
|
|
507
|
+
if (options?.ask) {
|
|
508
|
+
const { speakText, listenSpeechToText, promptInteractionMode } = await import('../engine/voiceEngine.js');
|
|
509
|
+
const modeChoice = await promptInteractionMode();
|
|
510
|
+
const isAudioMode = modeChoice === 'audio';
|
|
511
|
+
safeNote(chalk.cyan(`Interactive AI Q&A session active${targetLang ? ` (${targetLang})` : ''} [${isAudioMode ? ' Audio Mode' : ' Text Mode'}].\nAsk any question about this codebase. Press Ctrl+C to exit at any time.`), ' Interactive Q&A Mode');
|
|
512
|
+
const chatHistory = [];
|
|
513
|
+
while (true) {
|
|
514
|
+
let qText = '';
|
|
515
|
+
if (isAudioMode) {
|
|
516
|
+
safeNote(chalk.cyan(' Listening to your microphone for repository query...'), ' Scout Audio Interaction');
|
|
517
|
+
const transcript = await listenSpeechToText();
|
|
518
|
+
if (transcript && transcript.trim()) {
|
|
519
|
+
qText = transcript.trim();
|
|
520
|
+
safeNote(` Voice Question: "${chalk.bold.green(qText)}"`, ' Scout STT Input');
|
|
521
|
+
}
|
|
522
|
+
else {
|
|
523
|
+
safeNote(chalk.yellow('No speech transcript detected or listener timed out.'), ' Scout Voice Input');
|
|
524
|
+
continue;
|
|
525
|
+
}
|
|
526
|
+
}
|
|
527
|
+
else {
|
|
528
|
+
const query = await text({
|
|
529
|
+
message: chalk.bold('Ask a query about this repository:'),
|
|
530
|
+
placeholder: 'e.g. How does authentication work? (Ctrl+C to exit)',
|
|
531
|
+
validate: (value) => {
|
|
532
|
+
if (!value || !value.trim())
|
|
533
|
+
return 'Query cannot be empty';
|
|
534
|
+
return undefined;
|
|
535
|
+
},
|
|
536
|
+
});
|
|
537
|
+
if (isCancel(query)) {
|
|
538
|
+
safeNote(chalk.yellow('Exiting interactive session. Goodbye!'), ' Q&A Ended');
|
|
539
|
+
break;
|
|
540
|
+
}
|
|
541
|
+
qText = query.trim();
|
|
542
|
+
}
|
|
543
|
+
if (['exit', 'quit', 'q'].includes(qText.toLowerCase())) {
|
|
544
|
+
safeNote(chalk.yellow('Exiting interactive session. Goodbye!'), ' Q&A Ended');
|
|
545
|
+
break;
|
|
546
|
+
}
|
|
547
|
+
const askSpinner = spinner();
|
|
548
|
+
const isWebNeeded = options?.web ||
|
|
549
|
+
/^\/(web|internet|search)\b/i.test(qText) ||
|
|
550
|
+
/\b(search the web|internet|online docs|latest docs|web search)\b/i.test(qText);
|
|
551
|
+
if (isWebNeeded) {
|
|
552
|
+
askSpinner.start('Searching internet & analyzing codebase context...');
|
|
553
|
+
}
|
|
554
|
+
else {
|
|
555
|
+
askSpinner.start('Analyzing codebase');
|
|
556
|
+
}
|
|
557
|
+
const answer = await askRepoQuestion(qText, projectName, filesList, manifestContent || '{}', chatHistory, { useWeb: options?.web, language: targetLang, native: options?.native });
|
|
558
|
+
askSpinner.stop('Response:');
|
|
559
|
+
const formattedAnswer = renderMarkdown(answer);
|
|
560
|
+
safeNote(formattedAnswer, ` ${qText}`);
|
|
561
|
+
if (isAudioMode) {
|
|
562
|
+
await speakText(answer);
|
|
563
|
+
}
|
|
564
|
+
chatHistory.push({ role: 'user', content: qText });
|
|
565
|
+
chatHistory.push({ role: 'assistant', content: answer });
|
|
566
|
+
}
|
|
567
|
+
}
|
|
239
568
|
}
|
|
240
569
|
//# sourceMappingURL=init.js.map
|