@justmpm/ai-tool 3.22.2 → 3.23.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.
|
@@ -2002,6 +2002,24 @@ Use: ${hint("changes", ctx)} --file=<arquivo>
|
|
|
2002
2002
|
`;
|
|
2003
2003
|
}
|
|
2004
2004
|
}
|
|
2005
|
+
if (file.astDiff && file.astDiff.astApplied) {
|
|
2006
|
+
out += `
|
|
2007
|
+
AST ANALYSIS (${file.astDiff.changeType})
|
|
2008
|
+
`;
|
|
2009
|
+
for (const category of file.astDiff.categories) {
|
|
2010
|
+
if (category.entries.length === 0) continue;
|
|
2011
|
+
const catIcon = formatAstCategoryIcon(category.name);
|
|
2012
|
+
out += ` ${catIcon} ${category.name} (${category.entries.length})
|
|
2013
|
+
`;
|
|
2014
|
+
for (const entry of category.entries) {
|
|
2015
|
+
const prefix = entry.type === "added" ? "+" : entry.type === "removed" ? "-" : "~";
|
|
2016
|
+
const detail = entry.detail ? ` - ${entry.detail}` : "";
|
|
2017
|
+
const count = entry.count && entry.count > 1 ? ` (x${entry.count})` : "";
|
|
2018
|
+
out += ` ${prefix} ${entry.name}${detail}${count}
|
|
2019
|
+
`;
|
|
2020
|
+
}
|
|
2021
|
+
}
|
|
2022
|
+
}
|
|
2005
2023
|
}
|
|
2006
2024
|
if (newFiles.length > 0) {
|
|
2007
2025
|
out += `
|
|
@@ -2077,6 +2095,24 @@ function formatKindIcon(kind) {
|
|
|
2077
2095
|
return "+";
|
|
2078
2096
|
}
|
|
2079
2097
|
}
|
|
2098
|
+
function formatAstCategoryIcon(name) {
|
|
2099
|
+
switch (name) {
|
|
2100
|
+
case "JSX COMPONENTS":
|
|
2101
|
+
return "<>";
|
|
2102
|
+
case "PROPS":
|
|
2103
|
+
return "{}";
|
|
2104
|
+
case "DECLARATIONS":
|
|
2105
|
+
return "fn";
|
|
2106
|
+
case "IMPORTS":
|
|
2107
|
+
return "->";
|
|
2108
|
+
case "STRINGS":
|
|
2109
|
+
return '""';
|
|
2110
|
+
case "STRUCTURAL":
|
|
2111
|
+
return "##";
|
|
2112
|
+
default:
|
|
2113
|
+
return "..";
|
|
2114
|
+
}
|
|
2115
|
+
}
|
|
2080
2116
|
var MAX_EXAMPLES_PER_SYMBOL = 3;
|
|
2081
2117
|
function formatUsageExamples(examples, indent = 2) {
|
|
2082
2118
|
const prefix = " ".repeat(indent);
|
|
@@ -5286,7 +5322,7 @@ function enrichSuggestionsWithUsages(suggestions, index, cwd) {
|
|
|
5286
5322
|
}
|
|
5287
5323
|
|
|
5288
5324
|
// src/integrations/git.ts
|
|
5289
|
-
import { existsSync as existsSync8 } from "fs";
|
|
5325
|
+
import { existsSync as existsSync8, readFileSync as readFileSync6 } from "fs";
|
|
5290
5326
|
import { execSync as execSync2 } from "child_process";
|
|
5291
5327
|
function hasGitRepo(cwd) {
|
|
5292
5328
|
return existsSync8(cwd + "/.git");
|
|
@@ -5349,14 +5385,37 @@ function getUntrackedFiles(cwd) {
|
|
|
5349
5385
|
}
|
|
5350
5386
|
function getFullDiffText(target, cwd, filePath) {
|
|
5351
5387
|
if (!hasGitRepo(cwd)) return "";
|
|
5388
|
+
const fileArg = filePath ? ` -- "${filePath}"` : "";
|
|
5352
5389
|
try {
|
|
5390
|
+
if (target === "all") {
|
|
5391
|
+
let output2 = "";
|
|
5392
|
+
try {
|
|
5393
|
+
output2 += execSync2(`git diff --unified=10${fileArg} --cached`, {
|
|
5394
|
+
cwd,
|
|
5395
|
+
encoding: "utf-8",
|
|
5396
|
+
stdio: ["pipe", "pipe", "pipe"],
|
|
5397
|
+
maxBuffer: 10 * 1024 * 1024
|
|
5398
|
+
});
|
|
5399
|
+
} catch {
|
|
5400
|
+
}
|
|
5401
|
+
try {
|
|
5402
|
+
output2 += execSync2(`git diff --unified=10${fileArg}`, {
|
|
5403
|
+
cwd,
|
|
5404
|
+
encoding: "utf-8",
|
|
5405
|
+
stdio: ["pipe", "pipe", "pipe"],
|
|
5406
|
+
maxBuffer: 10 * 1024 * 1024
|
|
5407
|
+
});
|
|
5408
|
+
} catch {
|
|
5409
|
+
}
|
|
5410
|
+
return output2;
|
|
5411
|
+
}
|
|
5353
5412
|
const diffArgs = getDiffArgs(target);
|
|
5354
|
-
const fileArg = filePath ? ` -- "${filePath}"` : "";
|
|
5355
5413
|
const cmd = `git diff --unified=10${fileArg} ${diffArgs.join(" ")}`;
|
|
5356
5414
|
const output = execSync2(cmd, {
|
|
5357
5415
|
cwd,
|
|
5358
5416
|
encoding: "utf-8",
|
|
5359
|
-
stdio: ["pipe", "pipe", "pipe"]
|
|
5417
|
+
stdio: ["pipe", "pipe", "pipe"],
|
|
5418
|
+
maxBuffer: 10 * 1024 * 1024
|
|
5360
5419
|
});
|
|
5361
5420
|
return output;
|
|
5362
5421
|
} catch {
|
|
@@ -5638,7 +5697,7 @@ async function suggest(target, options = {}) {
|
|
|
5638
5697
|
}
|
|
5639
5698
|
|
|
5640
5699
|
// src/commands/context.ts
|
|
5641
|
-
import { existsSync as existsSync9, readFileSync as
|
|
5700
|
+
import { existsSync as existsSync9, readFileSync as readFileSync7 } from "fs";
|
|
5642
5701
|
import { resolve as resolve3, join as join9 } from "path";
|
|
5643
5702
|
import { SyntaxKind as SyntaxKind3 } from "ts-morph";
|
|
5644
5703
|
|
|
@@ -6102,7 +6161,7 @@ function findUsageInFile2(symbolName, filePath, maxExamples, cwd) {
|
|
|
6102
6161
|
return examples;
|
|
6103
6162
|
}
|
|
6104
6163
|
try {
|
|
6105
|
-
const content =
|
|
6164
|
+
const content = readFileSync7(absolutePath, "utf-8");
|
|
6106
6165
|
const lines = content.split("\n");
|
|
6107
6166
|
const usageRegex = new RegExp(`\\b${escapeRegex2(symbolName)}\\b`);
|
|
6108
6167
|
for (let i = 0; i < lines.length; i++) {
|
|
@@ -7275,9 +7334,423 @@ function getTriggerIcon(trigger) {
|
|
|
7275
7334
|
|
|
7276
7335
|
// src/commands/changes.ts
|
|
7277
7336
|
import * as path from "path";
|
|
7278
|
-
import { readFileSync as
|
|
7337
|
+
import { readFileSync as readFileSync9 } from "fs";
|
|
7279
7338
|
import parseGitDiff from "parse-git-diff";
|
|
7339
|
+
|
|
7340
|
+
// src/commands/ast-diff.ts
|
|
7341
|
+
import { execSync as execSync3 } from "child_process";
|
|
7342
|
+
import { existsSync as existsSync10, readFileSync as readFileSync8 } from "fs";
|
|
7343
|
+
import { join as join11, extname as extname3 } from "path";
|
|
7344
|
+
import {
|
|
7345
|
+
Project as Project3,
|
|
7346
|
+
SyntaxKind as SyntaxKind4
|
|
7347
|
+
} from "ts-morph";
|
|
7348
|
+
var MAX_ENTRIES_PER_CATEGORY = 100;
|
|
7349
|
+
var TS_EXTENSIONS = /* @__PURE__ */ new Set([".ts", ".tsx"]);
|
|
7350
|
+
var EMPTY_RESULT = (filePath) => ({
|
|
7351
|
+
filePath,
|
|
7352
|
+
changeType: "mixed",
|
|
7353
|
+
stats: { added: 0, removed: 0 },
|
|
7354
|
+
categories: []
|
|
7355
|
+
});
|
|
7356
|
+
function compareByName(oldItems, newItems, getName, getValue) {
|
|
7357
|
+
const entries = [];
|
|
7358
|
+
const oldMap = new Map(oldItems.map((item) => [getName(item), item]));
|
|
7359
|
+
const newMap = new Map(newItems.map((item) => [getName(item), item]));
|
|
7360
|
+
for (const [name] of oldMap) {
|
|
7361
|
+
if (!newMap.has(name)) {
|
|
7362
|
+
entries.push({ type: "removed", name });
|
|
7363
|
+
}
|
|
7364
|
+
}
|
|
7365
|
+
for (const [name] of newMap) {
|
|
7366
|
+
if (!oldMap.has(name)) {
|
|
7367
|
+
entries.push({ type: "added", name });
|
|
7368
|
+
}
|
|
7369
|
+
}
|
|
7370
|
+
if (getValue) {
|
|
7371
|
+
for (const [name, oldItem] of oldMap) {
|
|
7372
|
+
const newItem = newMap.get(name);
|
|
7373
|
+
if (newItem && getValue(oldItem) !== getValue(newItem)) {
|
|
7374
|
+
entries.push({ type: "modified", name });
|
|
7375
|
+
}
|
|
7376
|
+
}
|
|
7377
|
+
}
|
|
7378
|
+
return entries;
|
|
7379
|
+
}
|
|
7380
|
+
function classifyChangeType(stats, categories) {
|
|
7381
|
+
const totalLines = stats.added + stats.removed;
|
|
7382
|
+
const totalEntries = categories.reduce((sum, cat) => sum + cat.entries.length, 0);
|
|
7383
|
+
const jsxCount = categories.find((c) => c.name === "JSX COMPONENTS")?.entries.length ?? 0;
|
|
7384
|
+
const propsCount = categories.find((c) => c.name === "PROPS")?.entries.length ?? 0;
|
|
7385
|
+
const declCount = categories.find((c) => c.name === "DECLARATIONS")?.entries.length ?? 0;
|
|
7386
|
+
const addedEntries = categories.reduce(
|
|
7387
|
+
(sum, cat) => sum + cat.entries.filter((e) => e.type === "added").length,
|
|
7388
|
+
0
|
|
7389
|
+
);
|
|
7390
|
+
const modifiedEntries = categories.reduce(
|
|
7391
|
+
(sum, cat) => sum + cat.entries.filter((e) => e.type === "modified").length,
|
|
7392
|
+
0
|
|
7393
|
+
);
|
|
7394
|
+
if (totalLines > 100 && addedEntries < 3 && jsxCount + propsCount > 5) {
|
|
7395
|
+
return "structural";
|
|
7396
|
+
}
|
|
7397
|
+
if (addedEntries > 3 || declCount > 2 && addedEntries > 1) {
|
|
7398
|
+
return "new-feature";
|
|
7399
|
+
}
|
|
7400
|
+
if (totalLines < 50 && modifiedEntries > addedEntries && totalEntries < 20) {
|
|
7401
|
+
return "correction";
|
|
7402
|
+
}
|
|
7403
|
+
return "mixed";
|
|
7404
|
+
}
|
|
7405
|
+
function extractAttributesFromElement(el) {
|
|
7406
|
+
const props = /* @__PURE__ */ new Map();
|
|
7407
|
+
const attrs = el.getDescendantsOfKind(SyntaxKind4.JsxAttribute);
|
|
7408
|
+
for (const attr of attrs) {
|
|
7409
|
+
const name = attr.getNameNode().getText();
|
|
7410
|
+
const value = attr.getInitializer()?.getText() ?? "";
|
|
7411
|
+
props.set(name, value);
|
|
7412
|
+
}
|
|
7413
|
+
return props;
|
|
7414
|
+
}
|
|
7415
|
+
function extractImportsFromFile(sourceFile) {
|
|
7416
|
+
const imports = [];
|
|
7417
|
+
for (const decl of sourceFile.getImportDeclarations()) {
|
|
7418
|
+
const specifiers = [];
|
|
7419
|
+
const defaultImport = decl.getDefaultImport();
|
|
7420
|
+
if (defaultImport) {
|
|
7421
|
+
specifiers.push(defaultImport.getText());
|
|
7422
|
+
}
|
|
7423
|
+
for (const named of decl.getNamedImports()) {
|
|
7424
|
+
const alias = named.getAliasNode();
|
|
7425
|
+
if (alias) {
|
|
7426
|
+
specifiers.push(`${named.getName()} as ${alias.getText()}`);
|
|
7427
|
+
} else {
|
|
7428
|
+
specifiers.push(named.getName());
|
|
7429
|
+
}
|
|
7430
|
+
}
|
|
7431
|
+
const namespace = decl.getNamespaceImport();
|
|
7432
|
+
if (namespace) {
|
|
7433
|
+
specifiers.push(`* as ${namespace.getText()}`);
|
|
7434
|
+
}
|
|
7435
|
+
imports.push({
|
|
7436
|
+
module: decl.getModuleSpecifierValue(),
|
|
7437
|
+
specifiers: specifiers.join(", ")
|
|
7438
|
+
});
|
|
7439
|
+
}
|
|
7440
|
+
return imports;
|
|
7441
|
+
}
|
|
7442
|
+
function getOldContent(filePath, cwd, target) {
|
|
7443
|
+
try {
|
|
7444
|
+
const gitRef = target === "staged" ? `:${filePath}` : `HEAD:${filePath}`;
|
|
7445
|
+
return execSync3(`git show ${gitRef}`, { cwd, encoding: "utf-8", stdio: ["pipe", "pipe", "pipe"] });
|
|
7446
|
+
} catch {
|
|
7447
|
+
return null;
|
|
7448
|
+
}
|
|
7449
|
+
}
|
|
7450
|
+
function countLineChanges(oldContent, newContent) {
|
|
7451
|
+
const oldLines = oldContent.split("\n");
|
|
7452
|
+
const newLines = newContent.split("\n");
|
|
7453
|
+
const oldSet = new Set(oldLines);
|
|
7454
|
+
const newSet = new Set(newLines);
|
|
7455
|
+
const added = newLines.filter((line) => !oldSet.has(line)).length;
|
|
7456
|
+
const removed = oldLines.filter((line) => !newSet.has(line)).length;
|
|
7457
|
+
return { added, removed };
|
|
7458
|
+
}
|
|
7459
|
+
function compareComponentProps(oldProps, newProps, componentName) {
|
|
7460
|
+
const entries = [];
|
|
7461
|
+
for (const [name] of oldProps) {
|
|
7462
|
+
if (!newProps.has(name)) {
|
|
7463
|
+
entries.push({
|
|
7464
|
+
type: "removed",
|
|
7465
|
+
name: `${componentName}.${name}`,
|
|
7466
|
+
detail: `${name}=${oldProps.get(name)}`
|
|
7467
|
+
});
|
|
7468
|
+
}
|
|
7469
|
+
}
|
|
7470
|
+
for (const [name, value] of newProps) {
|
|
7471
|
+
if (!oldProps.has(name)) {
|
|
7472
|
+
entries.push({
|
|
7473
|
+
type: "added",
|
|
7474
|
+
name: `${componentName}.${name}`,
|
|
7475
|
+
detail: `${name}=${value}`
|
|
7476
|
+
});
|
|
7477
|
+
}
|
|
7478
|
+
}
|
|
7479
|
+
for (const [name, oldValue] of oldProps) {
|
|
7480
|
+
const newValue = newProps.get(name);
|
|
7481
|
+
if (newValue !== void 0 && oldValue !== newValue) {
|
|
7482
|
+
entries.push({
|
|
7483
|
+
type: "modified",
|
|
7484
|
+
name: `${componentName}.${name}`,
|
|
7485
|
+
detail: `${oldValue} -> ${newValue}`
|
|
7486
|
+
});
|
|
7487
|
+
}
|
|
7488
|
+
}
|
|
7489
|
+
return entries;
|
|
7490
|
+
}
|
|
7491
|
+
function detectStructuralChanges(oldContent, newContent, detectedChanges) {
|
|
7492
|
+
const entries = [];
|
|
7493
|
+
const lineStats = countLineChanges(oldContent, newContent);
|
|
7494
|
+
const unexplainedLines = lineStats.added + lineStats.removed - detectedChanges * 2;
|
|
7495
|
+
if (unexplainedLines > 20) {
|
|
7496
|
+
entries.push({
|
|
7497
|
+
type: "modified",
|
|
7498
|
+
name: "layout/structure",
|
|
7499
|
+
detail: `~${unexplainedLines} linhas nao explicadas por declaracoes individuais`
|
|
7500
|
+
});
|
|
7501
|
+
}
|
|
7502
|
+
const stylePatterns = [/\bsx=\{/, /\bclassName=/, /\bstyle=\{/];
|
|
7503
|
+
const newStyleMatches = stylePatterns.reduce(
|
|
7504
|
+
(count, pattern) => count + (newContent.match(pattern)?.length ?? 0),
|
|
7505
|
+
0
|
|
7506
|
+
);
|
|
7507
|
+
const oldStyleMatches = stylePatterns.reduce(
|
|
7508
|
+
(count, pattern) => count + (oldContent.match(pattern)?.length ?? 0),
|
|
7509
|
+
0
|
|
7510
|
+
);
|
|
7511
|
+
if (newStyleMatches !== oldStyleMatches) {
|
|
7512
|
+
const diff = newStyleMatches - oldStyleMatches;
|
|
7513
|
+
if (diff > 0) {
|
|
7514
|
+
entries.push({
|
|
7515
|
+
type: "added",
|
|
7516
|
+
name: "inline-styles",
|
|
7517
|
+
detail: `${diff} atributo(s) de estilo adicionado(s)`
|
|
7518
|
+
});
|
|
7519
|
+
} else {
|
|
7520
|
+
entries.push({
|
|
7521
|
+
type: "removed",
|
|
7522
|
+
name: "inline-styles",
|
|
7523
|
+
detail: `${Math.abs(diff)} atributo(s) de estilo removido(s)`
|
|
7524
|
+
});
|
|
7525
|
+
}
|
|
7526
|
+
}
|
|
7527
|
+
return entries;
|
|
7528
|
+
}
|
|
7529
|
+
async function astDiff(options) {
|
|
7530
|
+
const { filePath, cwd, target } = options;
|
|
7531
|
+
const fullPath = join11(cwd, filePath);
|
|
7532
|
+
const ext = extname3(filePath).toLowerCase();
|
|
7533
|
+
if (!TS_EXTENSIONS.has(ext)) {
|
|
7534
|
+
return EMPTY_RESULT(filePath);
|
|
7535
|
+
}
|
|
7536
|
+
if (!existsSync10(fullPath)) {
|
|
7537
|
+
return EMPTY_RESULT(filePath);
|
|
7538
|
+
}
|
|
7539
|
+
let newContent;
|
|
7540
|
+
try {
|
|
7541
|
+
newContent = readFileSync8(fullPath, "utf-8");
|
|
7542
|
+
} catch {
|
|
7543
|
+
return EMPTY_RESULT(filePath);
|
|
7544
|
+
}
|
|
7545
|
+
const oldContent = getOldContent(filePath, cwd, target);
|
|
7546
|
+
if (oldContent === null) {
|
|
7547
|
+
return {
|
|
7548
|
+
filePath,
|
|
7549
|
+
changeType: "new-feature",
|
|
7550
|
+
stats: { added: newContent.split("\n").length, removed: 0 },
|
|
7551
|
+
categories: []
|
|
7552
|
+
};
|
|
7553
|
+
}
|
|
7554
|
+
if (oldContent === newContent) {
|
|
7555
|
+
return EMPTY_RESULT(filePath);
|
|
7556
|
+
}
|
|
7557
|
+
try {
|
|
7558
|
+
const project = new Project3({ useInMemoryFileSystem: true });
|
|
7559
|
+
const oldFile = project.createSourceFile("old.tsx", oldContent);
|
|
7560
|
+
const newFile = project.createSourceFile("new.tsx", newContent);
|
|
7561
|
+
const categories = [];
|
|
7562
|
+
categories.push(compareJsxComponents(oldFile, newFile));
|
|
7563
|
+
categories.push(compareJsxProps(oldFile, newFile));
|
|
7564
|
+
categories.push(compareDeclarations(oldFile, newFile));
|
|
7565
|
+
categories.push(compareImports(oldFile, newFile));
|
|
7566
|
+
categories.push(compareStringLiterals(oldFile, newFile));
|
|
7567
|
+
const detectedChanges = categories.reduce((sum, cat) => sum + cat.entries.length, 0);
|
|
7568
|
+
const structuralEntries = detectStructuralChanges(oldContent, newContent, detectedChanges);
|
|
7569
|
+
if (structuralEntries.length > 0) {
|
|
7570
|
+
categories.push({ name: "STRUCTURAL", entries: structuralEntries });
|
|
7571
|
+
}
|
|
7572
|
+
for (const cat of categories) {
|
|
7573
|
+
if (cat.entries.length > MAX_ENTRIES_PER_CATEGORY) {
|
|
7574
|
+
cat.entries = cat.entries.slice(0, MAX_ENTRIES_PER_CATEGORY);
|
|
7575
|
+
}
|
|
7576
|
+
}
|
|
7577
|
+
const stats = countLineChanges(oldContent, newContent);
|
|
7578
|
+
const changeType = classifyChangeType(stats, categories);
|
|
7579
|
+
return { filePath, changeType, stats, categories };
|
|
7580
|
+
} catch {
|
|
7581
|
+
const stats = countLineChanges(oldContent, newContent);
|
|
7582
|
+
return {
|
|
7583
|
+
filePath,
|
|
7584
|
+
changeType: "mixed",
|
|
7585
|
+
stats,
|
|
7586
|
+
categories: []
|
|
7587
|
+
};
|
|
7588
|
+
}
|
|
7589
|
+
}
|
|
7590
|
+
function compareJsxComponents(oldFile, newFile) {
|
|
7591
|
+
const oldComponents = extractJsxComponentsFromSource(oldFile);
|
|
7592
|
+
const newComponents = extractJsxComponentsFromSource(newFile);
|
|
7593
|
+
const entries = [];
|
|
7594
|
+
for (const [name] of oldComponents) {
|
|
7595
|
+
if (!newComponents.has(name)) {
|
|
7596
|
+
entries.push({ type: "removed", name });
|
|
7597
|
+
}
|
|
7598
|
+
}
|
|
7599
|
+
for (const [name] of newComponents) {
|
|
7600
|
+
if (!oldComponents.has(name)) {
|
|
7601
|
+
entries.push({ type: "added", name });
|
|
7602
|
+
}
|
|
7603
|
+
}
|
|
7604
|
+
for (const [name, oldInfo] of oldComponents) {
|
|
7605
|
+
const newInfo = newComponents.get(name);
|
|
7606
|
+
if (newInfo) {
|
|
7607
|
+
const propChanges = compareComponentProps(oldInfo.props, newInfo.props, name);
|
|
7608
|
+
const modifiedProps = propChanges.filter((p) => p.type === "modified");
|
|
7609
|
+
if (modifiedProps.length > 0) {
|
|
7610
|
+
entries.push({
|
|
7611
|
+
type: "modified",
|
|
7612
|
+
name,
|
|
7613
|
+
detail: modifiedProps.map((p) => p.detail).join(", ")
|
|
7614
|
+
});
|
|
7615
|
+
}
|
|
7616
|
+
}
|
|
7617
|
+
}
|
|
7618
|
+
return { name: "JSX COMPONENTS", entries };
|
|
7619
|
+
}
|
|
7620
|
+
function compareJsxProps(oldFile, newFile) {
|
|
7621
|
+
const oldComponents = extractJsxComponentsFromSource(oldFile);
|
|
7622
|
+
const newComponents = extractJsxComponentsFromSource(newFile);
|
|
7623
|
+
const entries = [];
|
|
7624
|
+
for (const [name, newInfo] of newComponents) {
|
|
7625
|
+
const oldInfo = oldComponents.get(name);
|
|
7626
|
+
if (oldInfo) {
|
|
7627
|
+
const propChanges = compareComponentProps(oldInfo.props, newInfo.props, name);
|
|
7628
|
+
const addedRemoved = propChanges.filter((p) => p.type !== "modified");
|
|
7629
|
+
entries.push(...addedRemoved);
|
|
7630
|
+
}
|
|
7631
|
+
}
|
|
7632
|
+
return { name: "PROPS", entries };
|
|
7633
|
+
}
|
|
7634
|
+
function compareDeclarations(oldFile, newFile) {
|
|
7635
|
+
const oldDecls = extractDeclarationsFromSource(oldFile);
|
|
7636
|
+
const newDecls = extractDeclarationsFromSource(newFile);
|
|
7637
|
+
const entries = compareByName(
|
|
7638
|
+
oldDecls,
|
|
7639
|
+
newDecls,
|
|
7640
|
+
(d) => d.name,
|
|
7641
|
+
(d) => `${d.kind}:${d.name}`
|
|
7642
|
+
);
|
|
7643
|
+
for (const entry of entries) {
|
|
7644
|
+
const isNew = entry.type === "added";
|
|
7645
|
+
const source = isNew ? newDecls : oldDecls;
|
|
7646
|
+
const decl = source.find((d) => d.name === entry.name);
|
|
7647
|
+
if (decl) {
|
|
7648
|
+
entry.detail = `(${decl.kind})`;
|
|
7649
|
+
}
|
|
7650
|
+
}
|
|
7651
|
+
return { name: "DECLARATIONS", entries };
|
|
7652
|
+
}
|
|
7653
|
+
function compareImports(oldFile, newFile) {
|
|
7654
|
+
const oldImports = extractImportsFromFile(oldFile);
|
|
7655
|
+
const newImports = extractImportsFromFile(newFile);
|
|
7656
|
+
const entries = compareByName(
|
|
7657
|
+
oldImports,
|
|
7658
|
+
newImports,
|
|
7659
|
+
(i) => i.module,
|
|
7660
|
+
(i) => i.specifiers
|
|
7661
|
+
);
|
|
7662
|
+
return { name: "IMPORTS", entries };
|
|
7663
|
+
}
|
|
7664
|
+
function compareStringLiterals(oldFile, newFile) {
|
|
7665
|
+
const oldStrings = extractStringsFromSource(oldFile);
|
|
7666
|
+
const newStrings = extractStringsFromSource(newFile);
|
|
7667
|
+
const oldMap = /* @__PURE__ */ new Map();
|
|
7668
|
+
for (const s of oldStrings) {
|
|
7669
|
+
oldMap.set(s, (oldMap.get(s) ?? 0) + 1);
|
|
7670
|
+
}
|
|
7671
|
+
const newMap = /* @__PURE__ */ new Map();
|
|
7672
|
+
for (const s of newStrings) {
|
|
7673
|
+
newMap.set(s, (newMap.get(s) ?? 0) + 1);
|
|
7674
|
+
}
|
|
7675
|
+
const entries = [];
|
|
7676
|
+
for (const [str, oldCount] of oldMap) {
|
|
7677
|
+
const newCount = newMap.get(str) ?? 0;
|
|
7678
|
+
if (newCount < oldCount) {
|
|
7679
|
+
entries.push({
|
|
7680
|
+
type: "removed",
|
|
7681
|
+
name: `"${str}"`,
|
|
7682
|
+
count: oldCount - newCount
|
|
7683
|
+
});
|
|
7684
|
+
}
|
|
7685
|
+
}
|
|
7686
|
+
for (const [str, newCount] of newMap) {
|
|
7687
|
+
const oldCount = oldMap.get(str) ?? 0;
|
|
7688
|
+
if (newCount > oldCount) {
|
|
7689
|
+
entries.push({
|
|
7690
|
+
type: "added",
|
|
7691
|
+
name: `"${str}"`,
|
|
7692
|
+
count: newCount - oldCount
|
|
7693
|
+
});
|
|
7694
|
+
}
|
|
7695
|
+
}
|
|
7696
|
+
return { name: "STRINGS", entries };
|
|
7697
|
+
}
|
|
7698
|
+
function extractJsxComponentsFromSource(sourceFile) {
|
|
7699
|
+
const components = /* @__PURE__ */ new Map();
|
|
7700
|
+
const selfClosing = sourceFile.getDescendantsOfKind(SyntaxKind4.JsxSelfClosingElement);
|
|
7701
|
+
const opening = sourceFile.getDescendantsOfKind(SyntaxKind4.JsxOpeningElement);
|
|
7702
|
+
for (const el of [...selfClosing, ...opening]) {
|
|
7703
|
+
const name = el.getTagNameNode().getText();
|
|
7704
|
+
const props = extractAttributesFromElement(el);
|
|
7705
|
+
components.set(name, { name, props });
|
|
7706
|
+
}
|
|
7707
|
+
return components;
|
|
7708
|
+
}
|
|
7709
|
+
function extractDeclarationsFromSource(sourceFile) {
|
|
7710
|
+
const declarations = [];
|
|
7711
|
+
for (const fn of sourceFile.getFunctions()) {
|
|
7712
|
+
const name = fn.getName();
|
|
7713
|
+
if (name) {
|
|
7714
|
+
declarations.push({ name, kind: "function" });
|
|
7715
|
+
}
|
|
7716
|
+
}
|
|
7717
|
+
for (const v of sourceFile.getVariableDeclarations()) {
|
|
7718
|
+
const name = v.getName();
|
|
7719
|
+
if (name) {
|
|
7720
|
+
declarations.push({ name, kind: "const" });
|
|
7721
|
+
}
|
|
7722
|
+
}
|
|
7723
|
+
for (const iface of sourceFile.getInterfaces()) {
|
|
7724
|
+
declarations.push({ name: iface.getName(), kind: "interface" });
|
|
7725
|
+
}
|
|
7726
|
+
for (const t of sourceFile.getTypeAliases()) {
|
|
7727
|
+
declarations.push({ name: t.getName(), kind: "type" });
|
|
7728
|
+
}
|
|
7729
|
+
for (const e of sourceFile.getEnums()) {
|
|
7730
|
+
declarations.push({ name: e.getName(), kind: "enum" });
|
|
7731
|
+
}
|
|
7732
|
+
return declarations;
|
|
7733
|
+
}
|
|
7734
|
+
function extractStringsFromSource(sourceFile) {
|
|
7735
|
+
const literals = [];
|
|
7736
|
+
const strings = sourceFile.getDescendantsOfKind(SyntaxKind4.StringLiteral);
|
|
7737
|
+
for (const str of strings) {
|
|
7738
|
+
const parent = str.getParent();
|
|
7739
|
+
if (parent && (parent.getKind() === SyntaxKind4.ImportDeclaration || parent.getKind() === SyntaxKind4.ExportDeclaration)) {
|
|
7740
|
+
continue;
|
|
7741
|
+
}
|
|
7742
|
+
literals.push(str.getLiteralValue());
|
|
7743
|
+
}
|
|
7744
|
+
return literals;
|
|
7745
|
+
}
|
|
7746
|
+
|
|
7747
|
+
// src/commands/changes.ts
|
|
7280
7748
|
var UNTRACKED_PREVIEW_LINES = 20;
|
|
7749
|
+
var AST_DIFF_THRESHOLD = 50;
|
|
7750
|
+
function extIsTsOrTsx(filePath) {
|
|
7751
|
+
const ext = filePath.split(".").pop()?.toLowerCase();
|
|
7752
|
+
return ext === "ts" || ext === "tsx";
|
|
7753
|
+
}
|
|
7281
7754
|
async function changes(options = {}) {
|
|
7282
7755
|
const { cwd, format } = parseCommandOptions(options);
|
|
7283
7756
|
const ctx = options.ctx || "cli";
|
|
@@ -7327,6 +7800,19 @@ async function changes(options = {}) {
|
|
|
7327
7800
|
if (file.type === "DeletedFile") totalDeletedFiles++;
|
|
7328
7801
|
totalAdded += added;
|
|
7329
7802
|
totalRemoved += removed;
|
|
7803
|
+
if (options.file && extIsTsOrTsx(filePath) && added + removed > AST_DIFF_THRESHOLD) {
|
|
7804
|
+
try {
|
|
7805
|
+
const astResult = await astDiff({
|
|
7806
|
+
filePath,
|
|
7807
|
+
cwd,
|
|
7808
|
+
target
|
|
7809
|
+
});
|
|
7810
|
+
if (astResult.categories.length > 0) {
|
|
7811
|
+
change.astDiff = { ...astResult, astApplied: true };
|
|
7812
|
+
}
|
|
7813
|
+
} catch {
|
|
7814
|
+
}
|
|
7815
|
+
}
|
|
7330
7816
|
fileChanges.push(change);
|
|
7331
7817
|
}
|
|
7332
7818
|
}
|
|
@@ -7339,7 +7825,7 @@ async function changes(options = {}) {
|
|
|
7339
7825
|
const fullFilePath = path.resolve(cwd, filePath);
|
|
7340
7826
|
let lineCount = 0;
|
|
7341
7827
|
try {
|
|
7342
|
-
const content =
|
|
7828
|
+
const content = readFileSync9(fullFilePath, "utf-8");
|
|
7343
7829
|
const lines = content.split("\n");
|
|
7344
7830
|
lineCount = lines.length;
|
|
7345
7831
|
const syntheticDiff = lines.slice(0, UNTRACKED_PREVIEW_LINES).map((line) => `+${line}`).join("\n");
|
|
@@ -7941,8 +8427,8 @@ function extractGenericDiffPreview(diffText, filePath) {
|
|
|
7941
8427
|
}
|
|
7942
8428
|
|
|
7943
8429
|
// src/commands/find.ts
|
|
7944
|
-
import { existsSync as
|
|
7945
|
-
import { join as
|
|
8430
|
+
import { existsSync as existsSync11, readFileSync as readFileSync10 } from "fs";
|
|
8431
|
+
import { join as join12 } from "path";
|
|
7946
8432
|
|
|
7947
8433
|
// src/cache/gitHistory.ts
|
|
7948
8434
|
var memoryCache = /* @__PURE__ */ new Map();
|
|
@@ -8137,11 +8623,11 @@ function searchInIndex(index, query, filterType, allowedFiles, cwd) {
|
|
|
8137
8623
|
const baseCwd = cwd || "";
|
|
8138
8624
|
for (const filePath of importFiles) {
|
|
8139
8625
|
try {
|
|
8140
|
-
const fullPath = baseCwd ?
|
|
8141
|
-
if (!
|
|
8626
|
+
const fullPath = baseCwd ? join12(baseCwd, filePath) : filePath;
|
|
8627
|
+
if (!existsSync11(fullPath)) {
|
|
8142
8628
|
continue;
|
|
8143
8629
|
}
|
|
8144
|
-
const content =
|
|
8630
|
+
const content = readFileSync10(fullPath, "utf-8");
|
|
8145
8631
|
const lines = content.split("\n");
|
|
8146
8632
|
usageFilesScanned++;
|
|
8147
8633
|
const usageRegex = new RegExp(`\\b${escapeRegex3(query)}\\b`);
|
package/dist/cli.js
CHANGED
|
@@ -4,7 +4,7 @@ import {
|
|
|
4
4
|
depsInfo,
|
|
5
5
|
depsSearch,
|
|
6
6
|
describe
|
|
7
|
-
} from "./chunk-
|
|
7
|
+
} from "./chunk-4Z6PUAFR.js";
|
|
8
8
|
import {
|
|
9
9
|
VERSION,
|
|
10
10
|
area,
|
|
@@ -20,7 +20,7 @@ import {
|
|
|
20
20
|
impact,
|
|
21
21
|
map,
|
|
22
22
|
suggest
|
|
23
|
-
} from "./chunk-
|
|
23
|
+
} from "./chunk-WTRV35AO.js";
|
|
24
24
|
|
|
25
25
|
// src/cli.ts
|
|
26
26
|
import { resolve } from "path";
|
|
@@ -131,7 +131,7 @@ async function main() {
|
|
|
131
131
|
}
|
|
132
132
|
}
|
|
133
133
|
if (flags.mcp) {
|
|
134
|
-
const { startMcpServer } = await import("./server-
|
|
134
|
+
const { startMcpServer } = await import("./server-3HLEATUD.js");
|
|
135
135
|
await startMcpServer();
|
|
136
136
|
return;
|
|
137
137
|
}
|
package/dist/index.d.ts
CHANGED
|
@@ -406,6 +406,8 @@ interface FileChange {
|
|
|
406
406
|
renamedFrom?: string;
|
|
407
407
|
/** Tipo de mudança do arquivo no git */
|
|
408
408
|
fileType?: "added" | "deleted" | "changed" | "renamed";
|
|
409
|
+
/** Resultado AST diff quando disponível (só para --file com diff grande) */
|
|
410
|
+
astDiff?: AstDiffResult;
|
|
409
411
|
}
|
|
410
412
|
interface ChangesOptions extends CommandOptions {
|
|
411
413
|
target?: "staged" | "unstaged" | "all";
|
|
@@ -452,6 +454,39 @@ interface StabilityInfo {
|
|
|
452
454
|
/** Resumo de cambios recientes (máx 3) */
|
|
453
455
|
lastChanges: string[];
|
|
454
456
|
}
|
|
457
|
+
/** Tipo de mudança predominante detectada pelo AST diff */
|
|
458
|
+
type AstChangeType = "structural" | "new-feature" | "correction" | "mixed";
|
|
459
|
+
/** Categoria de mudança AST */
|
|
460
|
+
type AstDiffCategoryName = "JSX COMPONENTS" | "PROPS" | "DECLARATIONS" | "IMPORTS" | "STRINGS" | "STRUCTURAL";
|
|
461
|
+
/** Entry individual do diff AST */
|
|
462
|
+
interface AstDiffEntry {
|
|
463
|
+
type: "added" | "modified" | "removed";
|
|
464
|
+
name: string;
|
|
465
|
+
detail?: string;
|
|
466
|
+
line?: number;
|
|
467
|
+
/** Quantas ocorrências da mudança */
|
|
468
|
+
count?: number;
|
|
469
|
+
}
|
|
470
|
+
/** Categoria com entries agrupadas */
|
|
471
|
+
interface AstDiffCategory {
|
|
472
|
+
name: AstDiffCategoryName;
|
|
473
|
+
entries: AstDiffEntry[];
|
|
474
|
+
}
|
|
475
|
+
/** Resultado do AST diff */
|
|
476
|
+
interface AstDiffResult {
|
|
477
|
+
filePath: string;
|
|
478
|
+
/** Classificação do tipo de mudança predominante */
|
|
479
|
+
changeType: AstChangeType;
|
|
480
|
+
/** Total de linhas mudadas */
|
|
481
|
+
stats: {
|
|
482
|
+
added: number;
|
|
483
|
+
removed: number;
|
|
484
|
+
};
|
|
485
|
+
/** Entradas agrupadas por categoria */
|
|
486
|
+
categories: AstDiffCategory[];
|
|
487
|
+
/** Indica se AST diff foi aplicado (false = fallback para regex) */
|
|
488
|
+
astApplied: boolean;
|
|
489
|
+
}
|
|
455
490
|
|
|
456
491
|
/**
|
|
457
492
|
* Comando MAP - Mapa do projeto usando Skott
|
package/dist/index.js
CHANGED
|
@@ -3,7 +3,7 @@ import {
|
|
|
3
3
|
depsInfo,
|
|
4
4
|
depsSearch,
|
|
5
5
|
describe
|
|
6
|
-
} from "./chunk-
|
|
6
|
+
} from "./chunk-4Z6PUAFR.js";
|
|
7
7
|
import {
|
|
8
8
|
VERSION,
|
|
9
9
|
area,
|
|
@@ -19,7 +19,7 @@ import {
|
|
|
19
19
|
map,
|
|
20
20
|
recoveryHint,
|
|
21
21
|
suggest
|
|
22
|
-
} from "./chunk-
|
|
22
|
+
} from "./chunk-WTRV35AO.js";
|
|
23
23
|
|
|
24
24
|
// src/mcp/server.ts
|
|
25
25
|
import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@justmpm/ai-tool",
|
|
3
|
-
"version": "3.
|
|
3
|
+
"version": "3.23.0",
|
|
4
4
|
"description": "Ferramenta de análise de dependências e impacto para projetos TypeScript/JavaScript. Usa Skott + Knip internamente. Inclui busca por descrição, integração Git e testes inteligentes.",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"dependency-analysis",
|