@justmpm/ai-tool 3.22.3 → 3.24.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.
|
@@ -20,7 +20,7 @@ import {
|
|
|
20
20
|
readConfig,
|
|
21
21
|
recoveryHint,
|
|
22
22
|
simplifyType
|
|
23
|
-
} from "./chunk-
|
|
23
|
+
} from "./chunk-LKP6Z3JC.js";
|
|
24
24
|
|
|
25
25
|
// src/commands/describe.ts
|
|
26
26
|
var STOPWORDS = /* @__PURE__ */ new Set([
|
|
@@ -1625,25 +1625,29 @@ function isConceptualQuery(query, packageName) {
|
|
|
1625
1625
|
}
|
|
1626
1626
|
|
|
1627
1627
|
// src/commands/deps.ts
|
|
1628
|
+
function normalizeKind(kind) {
|
|
1629
|
+
if (kind === "interface" || kind === "enum") return "type";
|
|
1630
|
+
return kind;
|
|
1631
|
+
}
|
|
1628
1632
|
function normalizePath(p) {
|
|
1629
1633
|
return p.replace(/\\/g, "/");
|
|
1630
1634
|
}
|
|
1631
1635
|
function computeAvailableKinds(extracted, requestedKind) {
|
|
1632
1636
|
const kindCounts = /* @__PURE__ */ new Map();
|
|
1633
1637
|
for (const fn of extracted.functions) {
|
|
1634
|
-
const kind = classifySymbol("function", fn.name, fn.returnType);
|
|
1638
|
+
const kind = normalizeKind(classifySymbol("function", fn.name, fn.returnType));
|
|
1635
1639
|
if (isSemanticKindMatch(kind, requestedKind)) {
|
|
1636
1640
|
kindCounts.set(kind, (kindCounts.get(kind) ?? 0) + 1);
|
|
1637
1641
|
}
|
|
1638
1642
|
}
|
|
1639
1643
|
for (const t of extracted.types) {
|
|
1640
|
-
const kind = classifySymbol("type", t.name);
|
|
1644
|
+
const kind = normalizeKind(classifySymbol("type", t.name));
|
|
1641
1645
|
if (isSemanticKindMatch(kind, requestedKind)) {
|
|
1642
1646
|
kindCounts.set(kind, (kindCounts.get(kind) ?? 0) + 1);
|
|
1643
1647
|
}
|
|
1644
1648
|
}
|
|
1645
1649
|
for (const c of extracted.constants) {
|
|
1646
|
-
const kind = classifySymbol("const", c.name);
|
|
1650
|
+
const kind = normalizeKind(classifySymbol("const", c.name));
|
|
1647
1651
|
if (isSemanticKindMatch(kind, requestedKind)) {
|
|
1648
1652
|
kindCounts.set(kind, (kindCounts.get(kind) ?? 0) + 1);
|
|
1649
1653
|
}
|
|
@@ -340,8 +340,16 @@ function recoveryHint(errorType, ctx, _extra) {
|
|
|
340
340
|
return `
|
|
341
341
|
\u{1F4A1} Nenhuma area configurada neste projeto.
|
|
342
342
|
\u2192 ${hint("areas_init", ctx)} - gerar arquivo de configuracao
|
|
343
|
-
\u2192 Depois edite .analyze/areas.config.json com as areas do projeto
|
|
344
343
|
\u2192 ${hint("map", ctx)} - ver estrutura do projeto sem areas
|
|
344
|
+
|
|
345
|
+
Como configurar o areas.config.json:
|
|
346
|
+
- patterns: glob para pastas (ex: "app/dashboard/**", "hooks/usePets.*")
|
|
347
|
+
- keywords: substring no path, case-insensitive (ex: ["pet", "animal"])
|
|
348
|
+
- exclude: remove falsos positivos de uma area (ex: "components/pets/shared/**")
|
|
349
|
+
- descriptions: contexto manual para arquivos importantes (aparece no suggest, context, changes)
|
|
350
|
+
- settings.inferDescriptions: true para inferir descricoes automaticamente pelo nome do arquivo
|
|
351
|
+
- Um arquivo pode pertencer a multiplas areas
|
|
352
|
+
- Ideal: 5 a 15 areas (muitas = dificil de navegar)
|
|
345
353
|
`;
|
|
346
354
|
case "symbol_not_found":
|
|
347
355
|
return `
|
|
@@ -626,6 +634,8 @@ function formatMapSummary(result, areasInfo, ctx = "cli") {
|
|
|
626
634
|
out += ` \u2192 ${hint("areas_missing", ctx)} para ver quais arquivos estao sem area
|
|
627
635
|
`;
|
|
628
636
|
out += ` \u2192 ${hint("areas_init", ctx)} para criar/editar areas.config
|
|
637
|
+
`;
|
|
638
|
+
out += ` Boas praticas: patterns (glob exato) para pastas, keywords (case-insensitive) para arquivos espalhados, exclude para falsos positivos
|
|
629
639
|
|
|
630
640
|
`;
|
|
631
641
|
}
|
|
@@ -1895,65 +1905,6 @@ AFFECTED AREAS:
|
|
|
1895
1905
|
const semanticFiles = result.files.filter((f) => f.changes.length > 0 && !f.newFile);
|
|
1896
1906
|
const newFiles = result.files.filter((f) => f.newFile);
|
|
1897
1907
|
const otherFiles = result.files.filter((f) => f.changes.length === 0 && !f.newFile);
|
|
1898
|
-
const COMPACT_THRESHOLD = 10;
|
|
1899
|
-
const filesExceedThreshold = semanticFiles.length + newFiles.length > COMPACT_THRESHOLD;
|
|
1900
|
-
if (filesExceedThreshold) {
|
|
1901
|
-
out += `
|
|
1902
|
-
COMPACT MODE: Too many files. Use --file=<path> for details.
|
|
1903
|
-
|
|
1904
|
-
`;
|
|
1905
|
-
const modifiedCompact = result.files.filter((f) => !f.newFile);
|
|
1906
|
-
const renamedCompact = modifiedCompact.filter((f) => f.renamed);
|
|
1907
|
-
const otherModifiedCompact = modifiedCompact.filter((f) => !f.renamed);
|
|
1908
|
-
if (otherModifiedCompact.length > 0) {
|
|
1909
|
-
out += `Modified files:
|
|
1910
|
-
`;
|
|
1911
|
-
for (const file of otherModifiedCompact) {
|
|
1912
|
-
const changeCount = file.changes.length;
|
|
1913
|
-
out += ` ${file.path} (+${file.stats.added} -${file.stats.removed})`;
|
|
1914
|
-
if (changeCount > 0) {
|
|
1915
|
-
out += ` - ${changeCount} changes`;
|
|
1916
|
-
} else {
|
|
1917
|
-
out += ` - (no semantic changes)`;
|
|
1918
|
-
}
|
|
1919
|
-
out += `
|
|
1920
|
-
`;
|
|
1921
|
-
}
|
|
1922
|
-
}
|
|
1923
|
-
if (renamedCompact.length > 0) {
|
|
1924
|
-
out += `
|
|
1925
|
-
Renamed files:
|
|
1926
|
-
`;
|
|
1927
|
-
for (const file of renamedCompact) {
|
|
1928
|
-
out += ` ${file.renamedFrom} -> ${file.path} (+${file.stats.added} -${file.stats.removed})`;
|
|
1929
|
-
const changeCount = file.changes.length;
|
|
1930
|
-
if (changeCount > 0) {
|
|
1931
|
-
out += ` - ${changeCount} changes`;
|
|
1932
|
-
}
|
|
1933
|
-
out += `
|
|
1934
|
-
`;
|
|
1935
|
-
}
|
|
1936
|
-
}
|
|
1937
|
-
if (newFiles.length > 0) {
|
|
1938
|
-
out += `
|
|
1939
|
-
New files (untracked):
|
|
1940
|
-
`;
|
|
1941
|
-
for (const file of newFiles) {
|
|
1942
|
-
const changeCount = file.changes.length;
|
|
1943
|
-
out += ` ${file.path} (+${file.stats.added} new)`;
|
|
1944
|
-
if (changeCount > 0) {
|
|
1945
|
-
out += ` - ${changeCount} symbols`;
|
|
1946
|
-
}
|
|
1947
|
-
out += `
|
|
1948
|
-
`;
|
|
1949
|
-
}
|
|
1950
|
-
}
|
|
1951
|
-
out += `
|
|
1952
|
-
Use: ${hint("changes", ctx)} --file=<arquivo>
|
|
1953
|
-
`;
|
|
1954
|
-
out += nextSteps("changes", ctx);
|
|
1955
|
-
return out;
|
|
1956
|
-
}
|
|
1957
1908
|
for (const file of semanticFiles) {
|
|
1958
1909
|
out += `
|
|
1959
1910
|
--- ${file.path} (+${file.stats.added} -${file.stats.removed}) ---
|
|
@@ -2002,6 +1953,24 @@ Use: ${hint("changes", ctx)} --file=<arquivo>
|
|
|
2002
1953
|
`;
|
|
2003
1954
|
}
|
|
2004
1955
|
}
|
|
1956
|
+
if (file.astDiff && file.astDiff.astApplied) {
|
|
1957
|
+
out += `
|
|
1958
|
+
AST ANALYSIS (${file.astDiff.changeType})
|
|
1959
|
+
`;
|
|
1960
|
+
for (const category of file.astDiff.categories) {
|
|
1961
|
+
if (category.entries.length === 0) continue;
|
|
1962
|
+
const catIcon = formatAstCategoryIcon(category.name);
|
|
1963
|
+
out += ` ${catIcon} ${category.name} (${category.entries.length})
|
|
1964
|
+
`;
|
|
1965
|
+
for (const entry of category.entries) {
|
|
1966
|
+
const prefix = entry.type === "added" ? "+" : entry.type === "removed" ? "-" : "~";
|
|
1967
|
+
const detail = entry.detail ? ` - ${entry.detail}` : "";
|
|
1968
|
+
const count = entry.count && entry.count > 1 ? ` (x${entry.count})` : "";
|
|
1969
|
+
out += ` ${prefix} ${entry.name}${detail}${count}
|
|
1970
|
+
`;
|
|
1971
|
+
}
|
|
1972
|
+
}
|
|
1973
|
+
}
|
|
2005
1974
|
}
|
|
2006
1975
|
if (newFiles.length > 0) {
|
|
2007
1976
|
out += `
|
|
@@ -2077,6 +2046,24 @@ function formatKindIcon(kind) {
|
|
|
2077
2046
|
return "+";
|
|
2078
2047
|
}
|
|
2079
2048
|
}
|
|
2049
|
+
function formatAstCategoryIcon(name) {
|
|
2050
|
+
switch (name) {
|
|
2051
|
+
case "JSX COMPONENTS":
|
|
2052
|
+
return "<>";
|
|
2053
|
+
case "PROPS":
|
|
2054
|
+
return "{}";
|
|
2055
|
+
case "DECLARATIONS":
|
|
2056
|
+
return "fn";
|
|
2057
|
+
case "IMPORTS":
|
|
2058
|
+
return "->";
|
|
2059
|
+
case "STRINGS":
|
|
2060
|
+
return '""';
|
|
2061
|
+
case "STRUCTURAL":
|
|
2062
|
+
return "##";
|
|
2063
|
+
default:
|
|
2064
|
+
return "..";
|
|
2065
|
+
}
|
|
2066
|
+
}
|
|
2080
2067
|
var MAX_EXAMPLES_PER_SYMBOL = 3;
|
|
2081
2068
|
function formatUsageExamples(examples, indent = 2) {
|
|
2082
2069
|
const prefix = " ".repeat(indent);
|
|
@@ -5286,7 +5273,7 @@ function enrichSuggestionsWithUsages(suggestions, index, cwd) {
|
|
|
5286
5273
|
}
|
|
5287
5274
|
|
|
5288
5275
|
// src/integrations/git.ts
|
|
5289
|
-
import { existsSync as existsSync8 } from "fs";
|
|
5276
|
+
import { existsSync as existsSync8, readFileSync as readFileSync6 } from "fs";
|
|
5290
5277
|
import { execSync as execSync2 } from "child_process";
|
|
5291
5278
|
function hasGitRepo(cwd) {
|
|
5292
5279
|
return existsSync8(cwd + "/.git");
|
|
@@ -5661,7 +5648,7 @@ async function suggest(target, options = {}) {
|
|
|
5661
5648
|
}
|
|
5662
5649
|
|
|
5663
5650
|
// src/commands/context.ts
|
|
5664
|
-
import { existsSync as existsSync9, readFileSync as
|
|
5651
|
+
import { existsSync as existsSync9, readFileSync as readFileSync7 } from "fs";
|
|
5665
5652
|
import { resolve as resolve3, join as join9 } from "path";
|
|
5666
5653
|
import { SyntaxKind as SyntaxKind3 } from "ts-morph";
|
|
5667
5654
|
|
|
@@ -6125,7 +6112,7 @@ function findUsageInFile2(symbolName, filePath, maxExamples, cwd) {
|
|
|
6125
6112
|
return examples;
|
|
6126
6113
|
}
|
|
6127
6114
|
try {
|
|
6128
|
-
const content =
|
|
6115
|
+
const content = readFileSync7(absolutePath, "utf-8");
|
|
6129
6116
|
const lines = content.split("\n");
|
|
6130
6117
|
const usageRegex = new RegExp(`\\b${escapeRegex2(symbolName)}\\b`);
|
|
6131
6118
|
for (let i = 0; i < lines.length; i++) {
|
|
@@ -6969,14 +6956,20 @@ Ou edite manualmente o arquivo existente.
|
|
|
6969
6956
|
ai-tool area auth # Ver arquivos de uma \xE1rea
|
|
6970
6957
|
ai-tool map # Ver resumo do projeto
|
|
6971
6958
|
|
|
6972
|
-
\u{1F4A1} Dicas:
|
|
6959
|
+
\u{1F4A1} Dicas:
|
|
6973
6960
|
|
|
6974
|
-
\u2022
|
|
6975
|
-
\u2022
|
|
6976
|
-
\u2022 Um arquivo pode pertencer a m\xFAltiplas \xE1reas
|
|
6977
|
-
\u2022
|
|
6961
|
+
\u2022 patterns: glob para pastas (ex: "app/dashboard/**", "hooks/usePets.*")
|
|
6962
|
+
\u2022 keywords: substring no path inteiro, case-insensitive (ex: ["pet", "animal"])
|
|
6963
|
+
\u2022 Um arquivo pode pertencer a m\xFAltiplas \xE1reas
|
|
6964
|
+
\u2022 exclude: remove falsos positivos de uma \xE1rea (glob patterns)
|
|
6965
|
+
\u2022 descriptions (campo raiz): contexto manual para arquivos importantes
|
|
6966
|
+
Aparece nos comandos suggest, context e changes
|
|
6967
|
+
Ex: "src/hooks/useAuth.ts": "Hook principal de autentica\xE7\xE3o"
|
|
6968
|
+
\u2022 settings.inferDescriptions: true j\xE1 infere descri\xE7\xF5es automaticamente
|
|
6969
|
+
pelo nome do arquivo (ex: useAuth.ts \u2192 "Hook de auth", petService.ts \u2192 "Service de pet")
|
|
6970
|
+
Ativado por padr\xE3o no template gerado
|
|
6978
6971
|
|
|
6979
|
-
\u{1F4D6} Exemplo completo:
|
|
6972
|
+
\u{1F4D6} Exemplo completo:
|
|
6980
6973
|
|
|
6981
6974
|
{
|
|
6982
6975
|
"areas": {
|
|
@@ -7298,9 +7291,423 @@ function getTriggerIcon(trigger) {
|
|
|
7298
7291
|
|
|
7299
7292
|
// src/commands/changes.ts
|
|
7300
7293
|
import * as path from "path";
|
|
7301
|
-
import { readFileSync as
|
|
7294
|
+
import { readFileSync as readFileSync9 } from "fs";
|
|
7302
7295
|
import parseGitDiff from "parse-git-diff";
|
|
7296
|
+
|
|
7297
|
+
// src/commands/ast-diff.ts
|
|
7298
|
+
import { execSync as execSync3 } from "child_process";
|
|
7299
|
+
import { existsSync as existsSync10, readFileSync as readFileSync8 } from "fs";
|
|
7300
|
+
import { join as join11, extname as extname3 } from "path";
|
|
7301
|
+
import {
|
|
7302
|
+
Project as Project3,
|
|
7303
|
+
SyntaxKind as SyntaxKind4
|
|
7304
|
+
} from "ts-morph";
|
|
7305
|
+
var MAX_ENTRIES_PER_CATEGORY = 100;
|
|
7306
|
+
var TS_EXTENSIONS = /* @__PURE__ */ new Set([".ts", ".tsx"]);
|
|
7307
|
+
var EMPTY_RESULT = (filePath) => ({
|
|
7308
|
+
filePath,
|
|
7309
|
+
changeType: "mixed",
|
|
7310
|
+
stats: { added: 0, removed: 0 },
|
|
7311
|
+
categories: []
|
|
7312
|
+
});
|
|
7313
|
+
function compareByName(oldItems, newItems, getName, getValue) {
|
|
7314
|
+
const entries = [];
|
|
7315
|
+
const oldMap = new Map(oldItems.map((item) => [getName(item), item]));
|
|
7316
|
+
const newMap = new Map(newItems.map((item) => [getName(item), item]));
|
|
7317
|
+
for (const [name] of oldMap) {
|
|
7318
|
+
if (!newMap.has(name)) {
|
|
7319
|
+
entries.push({ type: "removed", name });
|
|
7320
|
+
}
|
|
7321
|
+
}
|
|
7322
|
+
for (const [name] of newMap) {
|
|
7323
|
+
if (!oldMap.has(name)) {
|
|
7324
|
+
entries.push({ type: "added", name });
|
|
7325
|
+
}
|
|
7326
|
+
}
|
|
7327
|
+
if (getValue) {
|
|
7328
|
+
for (const [name, oldItem] of oldMap) {
|
|
7329
|
+
const newItem = newMap.get(name);
|
|
7330
|
+
if (newItem && getValue(oldItem) !== getValue(newItem)) {
|
|
7331
|
+
entries.push({ type: "modified", name });
|
|
7332
|
+
}
|
|
7333
|
+
}
|
|
7334
|
+
}
|
|
7335
|
+
return entries;
|
|
7336
|
+
}
|
|
7337
|
+
function classifyChangeType(stats, categories) {
|
|
7338
|
+
const totalLines = stats.added + stats.removed;
|
|
7339
|
+
const totalEntries = categories.reduce((sum, cat) => sum + cat.entries.length, 0);
|
|
7340
|
+
const jsxCount = categories.find((c) => c.name === "JSX COMPONENTS")?.entries.length ?? 0;
|
|
7341
|
+
const propsCount = categories.find((c) => c.name === "PROPS")?.entries.length ?? 0;
|
|
7342
|
+
const declCount = categories.find((c) => c.name === "DECLARATIONS")?.entries.length ?? 0;
|
|
7343
|
+
const addedEntries = categories.reduce(
|
|
7344
|
+
(sum, cat) => sum + cat.entries.filter((e) => e.type === "added").length,
|
|
7345
|
+
0
|
|
7346
|
+
);
|
|
7347
|
+
const modifiedEntries = categories.reduce(
|
|
7348
|
+
(sum, cat) => sum + cat.entries.filter((e) => e.type === "modified").length,
|
|
7349
|
+
0
|
|
7350
|
+
);
|
|
7351
|
+
if (totalLines > 100 && addedEntries < 3 && jsxCount + propsCount > 5) {
|
|
7352
|
+
return "structural";
|
|
7353
|
+
}
|
|
7354
|
+
if (addedEntries > 3 || declCount > 2 && addedEntries > 1) {
|
|
7355
|
+
return "new-feature";
|
|
7356
|
+
}
|
|
7357
|
+
if (totalLines < 50 && modifiedEntries > addedEntries && totalEntries < 20) {
|
|
7358
|
+
return "correction";
|
|
7359
|
+
}
|
|
7360
|
+
return "mixed";
|
|
7361
|
+
}
|
|
7362
|
+
function extractAttributesFromElement(el) {
|
|
7363
|
+
const props = /* @__PURE__ */ new Map();
|
|
7364
|
+
const attrs = el.getDescendantsOfKind(SyntaxKind4.JsxAttribute);
|
|
7365
|
+
for (const attr of attrs) {
|
|
7366
|
+
const name = attr.getNameNode().getText();
|
|
7367
|
+
const value = attr.getInitializer()?.getText() ?? "";
|
|
7368
|
+
props.set(name, value);
|
|
7369
|
+
}
|
|
7370
|
+
return props;
|
|
7371
|
+
}
|
|
7372
|
+
function extractImportsFromFile(sourceFile) {
|
|
7373
|
+
const imports = [];
|
|
7374
|
+
for (const decl of sourceFile.getImportDeclarations()) {
|
|
7375
|
+
const specifiers = [];
|
|
7376
|
+
const defaultImport = decl.getDefaultImport();
|
|
7377
|
+
if (defaultImport) {
|
|
7378
|
+
specifiers.push(defaultImport.getText());
|
|
7379
|
+
}
|
|
7380
|
+
for (const named of decl.getNamedImports()) {
|
|
7381
|
+
const alias = named.getAliasNode();
|
|
7382
|
+
if (alias) {
|
|
7383
|
+
specifiers.push(`${named.getName()} as ${alias.getText()}`);
|
|
7384
|
+
} else {
|
|
7385
|
+
specifiers.push(named.getName());
|
|
7386
|
+
}
|
|
7387
|
+
}
|
|
7388
|
+
const namespace = decl.getNamespaceImport();
|
|
7389
|
+
if (namespace) {
|
|
7390
|
+
specifiers.push(`* as ${namespace.getText()}`);
|
|
7391
|
+
}
|
|
7392
|
+
imports.push({
|
|
7393
|
+
module: decl.getModuleSpecifierValue(),
|
|
7394
|
+
specifiers: specifiers.join(", ")
|
|
7395
|
+
});
|
|
7396
|
+
}
|
|
7397
|
+
return imports;
|
|
7398
|
+
}
|
|
7399
|
+
function getOldContent(filePath, cwd, target) {
|
|
7400
|
+
try {
|
|
7401
|
+
const gitRef = target === "staged" ? `:${filePath}` : `HEAD:${filePath}`;
|
|
7402
|
+
return execSync3(`git show ${gitRef}`, { cwd, encoding: "utf-8", stdio: ["pipe", "pipe", "pipe"] });
|
|
7403
|
+
} catch {
|
|
7404
|
+
return null;
|
|
7405
|
+
}
|
|
7406
|
+
}
|
|
7407
|
+
function countLineChanges(oldContent, newContent) {
|
|
7408
|
+
const oldLines = oldContent.split("\n");
|
|
7409
|
+
const newLines = newContent.split("\n");
|
|
7410
|
+
const oldSet = new Set(oldLines);
|
|
7411
|
+
const newSet = new Set(newLines);
|
|
7412
|
+
const added = newLines.filter((line) => !oldSet.has(line)).length;
|
|
7413
|
+
const removed = oldLines.filter((line) => !newSet.has(line)).length;
|
|
7414
|
+
return { added, removed };
|
|
7415
|
+
}
|
|
7416
|
+
function compareComponentProps(oldProps, newProps, componentName) {
|
|
7417
|
+
const entries = [];
|
|
7418
|
+
for (const [name] of oldProps) {
|
|
7419
|
+
if (!newProps.has(name)) {
|
|
7420
|
+
entries.push({
|
|
7421
|
+
type: "removed",
|
|
7422
|
+
name: `${componentName}.${name}`,
|
|
7423
|
+
detail: `${name}=${oldProps.get(name)}`
|
|
7424
|
+
});
|
|
7425
|
+
}
|
|
7426
|
+
}
|
|
7427
|
+
for (const [name, value] of newProps) {
|
|
7428
|
+
if (!oldProps.has(name)) {
|
|
7429
|
+
entries.push({
|
|
7430
|
+
type: "added",
|
|
7431
|
+
name: `${componentName}.${name}`,
|
|
7432
|
+
detail: `${name}=${value}`
|
|
7433
|
+
});
|
|
7434
|
+
}
|
|
7435
|
+
}
|
|
7436
|
+
for (const [name, oldValue] of oldProps) {
|
|
7437
|
+
const newValue = newProps.get(name);
|
|
7438
|
+
if (newValue !== void 0 && oldValue !== newValue) {
|
|
7439
|
+
entries.push({
|
|
7440
|
+
type: "modified",
|
|
7441
|
+
name: `${componentName}.${name}`,
|
|
7442
|
+
detail: `${oldValue} -> ${newValue}`
|
|
7443
|
+
});
|
|
7444
|
+
}
|
|
7445
|
+
}
|
|
7446
|
+
return entries;
|
|
7447
|
+
}
|
|
7448
|
+
function detectStructuralChanges(oldContent, newContent, detectedChanges) {
|
|
7449
|
+
const entries = [];
|
|
7450
|
+
const lineStats = countLineChanges(oldContent, newContent);
|
|
7451
|
+
const unexplainedLines = lineStats.added + lineStats.removed - detectedChanges * 2;
|
|
7452
|
+
if (unexplainedLines > 20) {
|
|
7453
|
+
entries.push({
|
|
7454
|
+
type: "modified",
|
|
7455
|
+
name: "layout/structure",
|
|
7456
|
+
detail: `~${unexplainedLines} linhas nao explicadas por declaracoes individuais`
|
|
7457
|
+
});
|
|
7458
|
+
}
|
|
7459
|
+
const stylePatterns = [/\bsx=\{/, /\bclassName=/, /\bstyle=\{/];
|
|
7460
|
+
const newStyleMatches = stylePatterns.reduce(
|
|
7461
|
+
(count, pattern) => count + (newContent.match(pattern)?.length ?? 0),
|
|
7462
|
+
0
|
|
7463
|
+
);
|
|
7464
|
+
const oldStyleMatches = stylePatterns.reduce(
|
|
7465
|
+
(count, pattern) => count + (oldContent.match(pattern)?.length ?? 0),
|
|
7466
|
+
0
|
|
7467
|
+
);
|
|
7468
|
+
if (newStyleMatches !== oldStyleMatches) {
|
|
7469
|
+
const diff = newStyleMatches - oldStyleMatches;
|
|
7470
|
+
if (diff > 0) {
|
|
7471
|
+
entries.push({
|
|
7472
|
+
type: "added",
|
|
7473
|
+
name: "inline-styles",
|
|
7474
|
+
detail: `${diff} atributo(s) de estilo adicionado(s)`
|
|
7475
|
+
});
|
|
7476
|
+
} else {
|
|
7477
|
+
entries.push({
|
|
7478
|
+
type: "removed",
|
|
7479
|
+
name: "inline-styles",
|
|
7480
|
+
detail: `${Math.abs(diff)} atributo(s) de estilo removido(s)`
|
|
7481
|
+
});
|
|
7482
|
+
}
|
|
7483
|
+
}
|
|
7484
|
+
return entries;
|
|
7485
|
+
}
|
|
7486
|
+
async function astDiff(options) {
|
|
7487
|
+
const { filePath, cwd, target } = options;
|
|
7488
|
+
const fullPath = join11(cwd, filePath);
|
|
7489
|
+
const ext = extname3(filePath).toLowerCase();
|
|
7490
|
+
if (!TS_EXTENSIONS.has(ext)) {
|
|
7491
|
+
return EMPTY_RESULT(filePath);
|
|
7492
|
+
}
|
|
7493
|
+
if (!existsSync10(fullPath)) {
|
|
7494
|
+
return EMPTY_RESULT(filePath);
|
|
7495
|
+
}
|
|
7496
|
+
let newContent;
|
|
7497
|
+
try {
|
|
7498
|
+
newContent = readFileSync8(fullPath, "utf-8");
|
|
7499
|
+
} catch {
|
|
7500
|
+
return EMPTY_RESULT(filePath);
|
|
7501
|
+
}
|
|
7502
|
+
const oldContent = getOldContent(filePath, cwd, target);
|
|
7503
|
+
if (oldContent === null) {
|
|
7504
|
+
return {
|
|
7505
|
+
filePath,
|
|
7506
|
+
changeType: "new-feature",
|
|
7507
|
+
stats: { added: newContent.split("\n").length, removed: 0 },
|
|
7508
|
+
categories: []
|
|
7509
|
+
};
|
|
7510
|
+
}
|
|
7511
|
+
if (oldContent === newContent) {
|
|
7512
|
+
return EMPTY_RESULT(filePath);
|
|
7513
|
+
}
|
|
7514
|
+
try {
|
|
7515
|
+
const project = new Project3({ useInMemoryFileSystem: true });
|
|
7516
|
+
const oldFile = project.createSourceFile("old.tsx", oldContent);
|
|
7517
|
+
const newFile = project.createSourceFile("new.tsx", newContent);
|
|
7518
|
+
const categories = [];
|
|
7519
|
+
categories.push(compareJsxComponents(oldFile, newFile));
|
|
7520
|
+
categories.push(compareJsxProps(oldFile, newFile));
|
|
7521
|
+
categories.push(compareDeclarations(oldFile, newFile));
|
|
7522
|
+
categories.push(compareImports(oldFile, newFile));
|
|
7523
|
+
categories.push(compareStringLiterals(oldFile, newFile));
|
|
7524
|
+
const detectedChanges = categories.reduce((sum, cat) => sum + cat.entries.length, 0);
|
|
7525
|
+
const structuralEntries = detectStructuralChanges(oldContent, newContent, detectedChanges);
|
|
7526
|
+
if (structuralEntries.length > 0) {
|
|
7527
|
+
categories.push({ name: "STRUCTURAL", entries: structuralEntries });
|
|
7528
|
+
}
|
|
7529
|
+
for (const cat of categories) {
|
|
7530
|
+
if (cat.entries.length > MAX_ENTRIES_PER_CATEGORY) {
|
|
7531
|
+
cat.entries = cat.entries.slice(0, MAX_ENTRIES_PER_CATEGORY);
|
|
7532
|
+
}
|
|
7533
|
+
}
|
|
7534
|
+
const stats = countLineChanges(oldContent, newContent);
|
|
7535
|
+
const changeType = classifyChangeType(stats, categories);
|
|
7536
|
+
return { filePath, changeType, stats, categories };
|
|
7537
|
+
} catch {
|
|
7538
|
+
const stats = countLineChanges(oldContent, newContent);
|
|
7539
|
+
return {
|
|
7540
|
+
filePath,
|
|
7541
|
+
changeType: "mixed",
|
|
7542
|
+
stats,
|
|
7543
|
+
categories: []
|
|
7544
|
+
};
|
|
7545
|
+
}
|
|
7546
|
+
}
|
|
7547
|
+
function compareJsxComponents(oldFile, newFile) {
|
|
7548
|
+
const oldComponents = extractJsxComponentsFromSource(oldFile);
|
|
7549
|
+
const newComponents = extractJsxComponentsFromSource(newFile);
|
|
7550
|
+
const entries = [];
|
|
7551
|
+
for (const [name] of oldComponents) {
|
|
7552
|
+
if (!newComponents.has(name)) {
|
|
7553
|
+
entries.push({ type: "removed", name });
|
|
7554
|
+
}
|
|
7555
|
+
}
|
|
7556
|
+
for (const [name] of newComponents) {
|
|
7557
|
+
if (!oldComponents.has(name)) {
|
|
7558
|
+
entries.push({ type: "added", name });
|
|
7559
|
+
}
|
|
7560
|
+
}
|
|
7561
|
+
for (const [name, oldInfo] of oldComponents) {
|
|
7562
|
+
const newInfo = newComponents.get(name);
|
|
7563
|
+
if (newInfo) {
|
|
7564
|
+
const propChanges = compareComponentProps(oldInfo.props, newInfo.props, name);
|
|
7565
|
+
const modifiedProps = propChanges.filter((p) => p.type === "modified");
|
|
7566
|
+
if (modifiedProps.length > 0) {
|
|
7567
|
+
entries.push({
|
|
7568
|
+
type: "modified",
|
|
7569
|
+
name,
|
|
7570
|
+
detail: modifiedProps.map((p) => p.detail).join(", ")
|
|
7571
|
+
});
|
|
7572
|
+
}
|
|
7573
|
+
}
|
|
7574
|
+
}
|
|
7575
|
+
return { name: "JSX COMPONENTS", entries };
|
|
7576
|
+
}
|
|
7577
|
+
function compareJsxProps(oldFile, newFile) {
|
|
7578
|
+
const oldComponents = extractJsxComponentsFromSource(oldFile);
|
|
7579
|
+
const newComponents = extractJsxComponentsFromSource(newFile);
|
|
7580
|
+
const entries = [];
|
|
7581
|
+
for (const [name, newInfo] of newComponents) {
|
|
7582
|
+
const oldInfo = oldComponents.get(name);
|
|
7583
|
+
if (oldInfo) {
|
|
7584
|
+
const propChanges = compareComponentProps(oldInfo.props, newInfo.props, name);
|
|
7585
|
+
const addedRemoved = propChanges.filter((p) => p.type !== "modified");
|
|
7586
|
+
entries.push(...addedRemoved);
|
|
7587
|
+
}
|
|
7588
|
+
}
|
|
7589
|
+
return { name: "PROPS", entries };
|
|
7590
|
+
}
|
|
7591
|
+
function compareDeclarations(oldFile, newFile) {
|
|
7592
|
+
const oldDecls = extractDeclarationsFromSource(oldFile);
|
|
7593
|
+
const newDecls = extractDeclarationsFromSource(newFile);
|
|
7594
|
+
const entries = compareByName(
|
|
7595
|
+
oldDecls,
|
|
7596
|
+
newDecls,
|
|
7597
|
+
(d) => d.name,
|
|
7598
|
+
(d) => `${d.kind}:${d.name}`
|
|
7599
|
+
);
|
|
7600
|
+
for (const entry of entries) {
|
|
7601
|
+
const isNew = entry.type === "added";
|
|
7602
|
+
const source = isNew ? newDecls : oldDecls;
|
|
7603
|
+
const decl = source.find((d) => d.name === entry.name);
|
|
7604
|
+
if (decl) {
|
|
7605
|
+
entry.detail = `(${decl.kind})`;
|
|
7606
|
+
}
|
|
7607
|
+
}
|
|
7608
|
+
return { name: "DECLARATIONS", entries };
|
|
7609
|
+
}
|
|
7610
|
+
function compareImports(oldFile, newFile) {
|
|
7611
|
+
const oldImports = extractImportsFromFile(oldFile);
|
|
7612
|
+
const newImports = extractImportsFromFile(newFile);
|
|
7613
|
+
const entries = compareByName(
|
|
7614
|
+
oldImports,
|
|
7615
|
+
newImports,
|
|
7616
|
+
(i) => i.module,
|
|
7617
|
+
(i) => i.specifiers
|
|
7618
|
+
);
|
|
7619
|
+
return { name: "IMPORTS", entries };
|
|
7620
|
+
}
|
|
7621
|
+
function compareStringLiterals(oldFile, newFile) {
|
|
7622
|
+
const oldStrings = extractStringsFromSource(oldFile);
|
|
7623
|
+
const newStrings = extractStringsFromSource(newFile);
|
|
7624
|
+
const oldMap = /* @__PURE__ */ new Map();
|
|
7625
|
+
for (const s of oldStrings) {
|
|
7626
|
+
oldMap.set(s, (oldMap.get(s) ?? 0) + 1);
|
|
7627
|
+
}
|
|
7628
|
+
const newMap = /* @__PURE__ */ new Map();
|
|
7629
|
+
for (const s of newStrings) {
|
|
7630
|
+
newMap.set(s, (newMap.get(s) ?? 0) + 1);
|
|
7631
|
+
}
|
|
7632
|
+
const entries = [];
|
|
7633
|
+
for (const [str, oldCount] of oldMap) {
|
|
7634
|
+
const newCount = newMap.get(str) ?? 0;
|
|
7635
|
+
if (newCount < oldCount) {
|
|
7636
|
+
entries.push({
|
|
7637
|
+
type: "removed",
|
|
7638
|
+
name: `"${str}"`,
|
|
7639
|
+
count: oldCount - newCount
|
|
7640
|
+
});
|
|
7641
|
+
}
|
|
7642
|
+
}
|
|
7643
|
+
for (const [str, newCount] of newMap) {
|
|
7644
|
+
const oldCount = oldMap.get(str) ?? 0;
|
|
7645
|
+
if (newCount > oldCount) {
|
|
7646
|
+
entries.push({
|
|
7647
|
+
type: "added",
|
|
7648
|
+
name: `"${str}"`,
|
|
7649
|
+
count: newCount - oldCount
|
|
7650
|
+
});
|
|
7651
|
+
}
|
|
7652
|
+
}
|
|
7653
|
+
return { name: "STRINGS", entries };
|
|
7654
|
+
}
|
|
7655
|
+
function extractJsxComponentsFromSource(sourceFile) {
|
|
7656
|
+
const components = /* @__PURE__ */ new Map();
|
|
7657
|
+
const selfClosing = sourceFile.getDescendantsOfKind(SyntaxKind4.JsxSelfClosingElement);
|
|
7658
|
+
const opening = sourceFile.getDescendantsOfKind(SyntaxKind4.JsxOpeningElement);
|
|
7659
|
+
for (const el of [...selfClosing, ...opening]) {
|
|
7660
|
+
const name = el.getTagNameNode().getText();
|
|
7661
|
+
const props = extractAttributesFromElement(el);
|
|
7662
|
+
components.set(name, { name, props });
|
|
7663
|
+
}
|
|
7664
|
+
return components;
|
|
7665
|
+
}
|
|
7666
|
+
function extractDeclarationsFromSource(sourceFile) {
|
|
7667
|
+
const declarations = [];
|
|
7668
|
+
for (const fn of sourceFile.getFunctions()) {
|
|
7669
|
+
const name = fn.getName();
|
|
7670
|
+
if (name) {
|
|
7671
|
+
declarations.push({ name, kind: "function" });
|
|
7672
|
+
}
|
|
7673
|
+
}
|
|
7674
|
+
for (const v of sourceFile.getVariableDeclarations()) {
|
|
7675
|
+
const name = v.getName();
|
|
7676
|
+
if (name) {
|
|
7677
|
+
declarations.push({ name, kind: "const" });
|
|
7678
|
+
}
|
|
7679
|
+
}
|
|
7680
|
+
for (const iface of sourceFile.getInterfaces()) {
|
|
7681
|
+
declarations.push({ name: iface.getName(), kind: "interface" });
|
|
7682
|
+
}
|
|
7683
|
+
for (const t of sourceFile.getTypeAliases()) {
|
|
7684
|
+
declarations.push({ name: t.getName(), kind: "type" });
|
|
7685
|
+
}
|
|
7686
|
+
for (const e of sourceFile.getEnums()) {
|
|
7687
|
+
declarations.push({ name: e.getName(), kind: "enum" });
|
|
7688
|
+
}
|
|
7689
|
+
return declarations;
|
|
7690
|
+
}
|
|
7691
|
+
function extractStringsFromSource(sourceFile) {
|
|
7692
|
+
const literals = [];
|
|
7693
|
+
const strings = sourceFile.getDescendantsOfKind(SyntaxKind4.StringLiteral);
|
|
7694
|
+
for (const str of strings) {
|
|
7695
|
+
const parent = str.getParent();
|
|
7696
|
+
if (parent && (parent.getKind() === SyntaxKind4.ImportDeclaration || parent.getKind() === SyntaxKind4.ExportDeclaration)) {
|
|
7697
|
+
continue;
|
|
7698
|
+
}
|
|
7699
|
+
literals.push(str.getLiteralValue());
|
|
7700
|
+
}
|
|
7701
|
+
return literals;
|
|
7702
|
+
}
|
|
7703
|
+
|
|
7704
|
+
// src/commands/changes.ts
|
|
7303
7705
|
var UNTRACKED_PREVIEW_LINES = 20;
|
|
7706
|
+
var AST_DIFF_THRESHOLD = 50;
|
|
7707
|
+
function extIsTsOrTsx(filePath) {
|
|
7708
|
+
const ext = filePath.split(".").pop()?.toLowerCase();
|
|
7709
|
+
return ext === "ts" || ext === "tsx";
|
|
7710
|
+
}
|
|
7304
7711
|
async function changes(options = {}) {
|
|
7305
7712
|
const { cwd, format } = parseCommandOptions(options);
|
|
7306
7713
|
const ctx = options.ctx || "cli";
|
|
@@ -7350,6 +7757,19 @@ async function changes(options = {}) {
|
|
|
7350
7757
|
if (file.type === "DeletedFile") totalDeletedFiles++;
|
|
7351
7758
|
totalAdded += added;
|
|
7352
7759
|
totalRemoved += removed;
|
|
7760
|
+
if (options.file && extIsTsOrTsx(filePath) && added + removed > AST_DIFF_THRESHOLD) {
|
|
7761
|
+
try {
|
|
7762
|
+
const astResult = await astDiff({
|
|
7763
|
+
filePath,
|
|
7764
|
+
cwd,
|
|
7765
|
+
target
|
|
7766
|
+
});
|
|
7767
|
+
if (astResult.categories.length > 0) {
|
|
7768
|
+
change.astDiff = { ...astResult, astApplied: true };
|
|
7769
|
+
}
|
|
7770
|
+
} catch {
|
|
7771
|
+
}
|
|
7772
|
+
}
|
|
7353
7773
|
fileChanges.push(change);
|
|
7354
7774
|
}
|
|
7355
7775
|
}
|
|
@@ -7362,7 +7782,7 @@ async function changes(options = {}) {
|
|
|
7362
7782
|
const fullFilePath = path.resolve(cwd, filePath);
|
|
7363
7783
|
let lineCount = 0;
|
|
7364
7784
|
try {
|
|
7365
|
-
const content =
|
|
7785
|
+
const content = readFileSync9(fullFilePath, "utf-8");
|
|
7366
7786
|
const lines = content.split("\n");
|
|
7367
7787
|
lineCount = lines.length;
|
|
7368
7788
|
const syntheticDiff = lines.slice(0, UNTRACKED_PREVIEW_LINES).map((line) => `+${line}`).join("\n");
|
|
@@ -7964,8 +8384,8 @@ function extractGenericDiffPreview(diffText, filePath) {
|
|
|
7964
8384
|
}
|
|
7965
8385
|
|
|
7966
8386
|
// src/commands/find.ts
|
|
7967
|
-
import { existsSync as
|
|
7968
|
-
import { join as
|
|
8387
|
+
import { existsSync as existsSync11, readFileSync as readFileSync10 } from "fs";
|
|
8388
|
+
import { join as join12 } from "path";
|
|
7969
8389
|
|
|
7970
8390
|
// src/cache/gitHistory.ts
|
|
7971
8391
|
var memoryCache = /* @__PURE__ */ new Map();
|
|
@@ -8160,11 +8580,11 @@ function searchInIndex(index, query, filterType, allowedFiles, cwd) {
|
|
|
8160
8580
|
const baseCwd = cwd || "";
|
|
8161
8581
|
for (const filePath of importFiles) {
|
|
8162
8582
|
try {
|
|
8163
|
-
const fullPath = baseCwd ?
|
|
8164
|
-
if (!
|
|
8583
|
+
const fullPath = baseCwd ? join12(baseCwd, filePath) : filePath;
|
|
8584
|
+
if (!existsSync11(fullPath)) {
|
|
8165
8585
|
continue;
|
|
8166
8586
|
}
|
|
8167
|
-
const content =
|
|
8587
|
+
const content = readFileSync10(fullPath, "utf-8");
|
|
8168
8588
|
const lines = content.split("\n");
|
|
8169
8589
|
usageFilesScanned++;
|
|
8170
8590
|
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-ERMF7MJF.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-LKP6Z3JC.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-T6IW5ZR7.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-ERMF7MJF.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-LKP6Z3JC.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.24.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",
|