@justmpm/ai-tool 1.0.4 → 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.
|
@@ -181,6 +181,10 @@ var COMMAND_MAP = {
|
|
|
181
181
|
cli: "ai-tool areas init",
|
|
182
182
|
mcp: "analyze__aitool_areas_init"
|
|
183
183
|
},
|
|
184
|
+
areas_missing: {
|
|
185
|
+
cli: "ai-tool areas --missing",
|
|
186
|
+
mcp: "analyze__aitool_list_areas { missing: true }"
|
|
187
|
+
},
|
|
184
188
|
find: {
|
|
185
189
|
cli: "ai-tool find <termo>",
|
|
186
190
|
mcp: "analyze__aitool_find { query: '<termo>' }"
|
|
@@ -192,6 +196,14 @@ var COMMAND_MAP = {
|
|
|
192
196
|
functions: {
|
|
193
197
|
cli: "ai-tool functions",
|
|
194
198
|
mcp: "analyze__aitool_list_functions"
|
|
199
|
+
},
|
|
200
|
+
changes: {
|
|
201
|
+
cli: "ai-tool changes",
|
|
202
|
+
mcp: "analyze__aitool_changes"
|
|
203
|
+
},
|
|
204
|
+
changes_staged: {
|
|
205
|
+
cli: "ai-tool changes --staged",
|
|
206
|
+
mcp: "analyze__aitool_changes { target: 'staged' }"
|
|
195
207
|
}
|
|
196
208
|
};
|
|
197
209
|
function hint(command, ctx, params) {
|
|
@@ -256,6 +268,11 @@ var NEXT_STEPS = {
|
|
|
256
268
|
{ command: "find", description: "buscar uma Cloud Function especifica" },
|
|
257
269
|
{ command: "impact", description: "ver impacto de modificar uma function" },
|
|
258
270
|
{ command: "context", description: "ver assinaturas de uma function" }
|
|
271
|
+
],
|
|
272
|
+
changes: [
|
|
273
|
+
{ command: "context", description: "ver assinaturas atuais de um arquivo modificado" },
|
|
274
|
+
{ command: "impact", description: "ver impacto das mudancas em outros arquivos" },
|
|
275
|
+
{ command: "suggest", description: "o que ler antes de editar um arquivo modificado" }
|
|
259
276
|
]
|
|
260
277
|
};
|
|
261
278
|
function nextSteps(command, ctx) {
|
|
@@ -471,7 +488,9 @@ function formatMapSummary(result, areasInfo, ctx = "cli") {
|
|
|
471
488
|
if (areasInfo && areasInfo.unmappedCount > 0) {
|
|
472
489
|
out += `\u26A0\uFE0F ${areasInfo.unmappedCount} arquivo(s) sem area definida
|
|
473
490
|
`;
|
|
474
|
-
out += ` \u2192 ${hint("
|
|
491
|
+
out += ` \u2192 ${hint("areas_missing", ctx)} para ver quais arquivos estao sem area
|
|
492
|
+
`;
|
|
493
|
+
out += ` \u2192 ${hint("areas_init", ctx)} para criar/editar areas.config
|
|
475
494
|
|
|
476
495
|
`;
|
|
477
496
|
}
|
|
@@ -1528,6 +1547,150 @@ function formatAreaContextText(result, ctx = "cli") {
|
|
|
1528
1547
|
out += nextSteps("area_context", ctx);
|
|
1529
1548
|
return out;
|
|
1530
1549
|
}
|
|
1550
|
+
function formatChangesText(result, ctx = "cli") {
|
|
1551
|
+
let out = "";
|
|
1552
|
+
out += `CHANGES SUMMARY
|
|
1553
|
+
`;
|
|
1554
|
+
if (result.files.length === 0) {
|
|
1555
|
+
out += `No changes detected.
|
|
1556
|
+
`;
|
|
1557
|
+
out += nextSteps("changes", ctx);
|
|
1558
|
+
return out;
|
|
1559
|
+
}
|
|
1560
|
+
const targetLabel = result.target === "staged" ? "staged" : result.target === "unstaged" ? "unstaged" : "unstaged + staged";
|
|
1561
|
+
out += `Files: ${result.summary.totalFiles} modified (${targetLabel}) | +${result.summary.totalAdded} -${result.summary.totalRemoved}
|
|
1562
|
+
`;
|
|
1563
|
+
const kindCounts = {};
|
|
1564
|
+
for (const file of result.files) {
|
|
1565
|
+
for (const entry of file.changes) {
|
|
1566
|
+
if (!kindCounts[entry.kind]) kindCounts[entry.kind] = { added: 0, modified: 0, removed: 0 };
|
|
1567
|
+
kindCounts[entry.kind][entry.type]++;
|
|
1568
|
+
}
|
|
1569
|
+
}
|
|
1570
|
+
const kindLabels = {
|
|
1571
|
+
function: "Functions",
|
|
1572
|
+
type: "Types",
|
|
1573
|
+
import: "Imports",
|
|
1574
|
+
const: "Consts",
|
|
1575
|
+
component: "Components",
|
|
1576
|
+
hook: "Hooks",
|
|
1577
|
+
enum: "Enums",
|
|
1578
|
+
class: "Classes",
|
|
1579
|
+
config: "Config",
|
|
1580
|
+
other: "Other"
|
|
1581
|
+
};
|
|
1582
|
+
const kindParts = [];
|
|
1583
|
+
for (const [kind, counts] of Object.entries(kindCounts)) {
|
|
1584
|
+
const parts = [];
|
|
1585
|
+
if (counts.added > 0) parts.push(`+${counts.added}`);
|
|
1586
|
+
if (counts.modified > 0) parts.push(`~${counts.modified}`);
|
|
1587
|
+
if (counts.removed > 0) parts.push(`-${counts.removed}`);
|
|
1588
|
+
if (parts.length > 0) kindParts.push(`${kindLabels[kind]}: ${parts.join(" ")}`);
|
|
1589
|
+
}
|
|
1590
|
+
if (kindParts.length > 0) {
|
|
1591
|
+
out += `${kindParts.join(" | ")}
|
|
1592
|
+
`;
|
|
1593
|
+
}
|
|
1594
|
+
if (result.affectedAreas && result.affectedAreas.length > 0) {
|
|
1595
|
+
out += `
|
|
1596
|
+
AFFECTED AREAS:
|
|
1597
|
+
`;
|
|
1598
|
+
for (const area2 of result.affectedAreas) {
|
|
1599
|
+
const desc = area2.description ? ` - ${area2.description}` : "";
|
|
1600
|
+
out += ` ${area2.name} (${area2.fileCount} file${area2.fileCount === 1 ? "" : "s"})${desc}
|
|
1601
|
+
`;
|
|
1602
|
+
}
|
|
1603
|
+
out += "\n";
|
|
1604
|
+
}
|
|
1605
|
+
const semanticFiles = result.files.filter((f) => f.changes.length > 0);
|
|
1606
|
+
const otherFiles = result.files.filter((f) => f.changes.length === 0);
|
|
1607
|
+
const COMPACT_THRESHOLD = 10;
|
|
1608
|
+
if (result.files.length > COMPACT_THRESHOLD) {
|
|
1609
|
+
out += `
|
|
1610
|
+
COMPACT MODE: Too many files. Use --file=<path> for details.
|
|
1611
|
+
|
|
1612
|
+
`;
|
|
1613
|
+
out += `Modified files:
|
|
1614
|
+
`;
|
|
1615
|
+
for (const file of result.files) {
|
|
1616
|
+
const changeCount = file.changes.length;
|
|
1617
|
+
out += ` ${file.path} (+${file.stats.added} -${file.stats.removed})`;
|
|
1618
|
+
if (changeCount > 0) {
|
|
1619
|
+
out += ` - ${changeCount} changes`;
|
|
1620
|
+
} else {
|
|
1621
|
+
out += ` - (no semantic changes)`;
|
|
1622
|
+
}
|
|
1623
|
+
out += `
|
|
1624
|
+
`;
|
|
1625
|
+
}
|
|
1626
|
+
out += `
|
|
1627
|
+
Use: ${hint("changes", ctx)} --file=<arquivo>
|
|
1628
|
+
`;
|
|
1629
|
+
out += nextSteps("changes", ctx);
|
|
1630
|
+
return out;
|
|
1631
|
+
}
|
|
1632
|
+
for (const file of semanticFiles) {
|
|
1633
|
+
out += `
|
|
1634
|
+
--- ${file.path} (+${file.stats.added} -${file.stats.removed}) ---
|
|
1635
|
+
`;
|
|
1636
|
+
const added = file.changes.filter((c) => c.type === "added");
|
|
1637
|
+
const modified = file.changes.filter((c) => c.type === "modified");
|
|
1638
|
+
const removed = file.changes.filter((c) => c.type === "removed");
|
|
1639
|
+
if (added.length > 0) {
|
|
1640
|
+
out += ` ADDED
|
|
1641
|
+
`;
|
|
1642
|
+
for (const entry of added) {
|
|
1643
|
+
const prefix = formatKindIcon(entry.kind);
|
|
1644
|
+
const detail = entry.detail ? ` - ${entry.detail}` : "";
|
|
1645
|
+
out += ` ${prefix} ${entry.name}${detail}
|
|
1646
|
+
`;
|
|
1647
|
+
}
|
|
1648
|
+
}
|
|
1649
|
+
if (modified.length > 0) {
|
|
1650
|
+
out += ` MODIFIED
|
|
1651
|
+
`;
|
|
1652
|
+
for (const entry of modified) {
|
|
1653
|
+
const detail = entry.detail ? ` - ${entry.detail}` : "";
|
|
1654
|
+
out += ` ~ ${entry.name}${detail}
|
|
1655
|
+
`;
|
|
1656
|
+
}
|
|
1657
|
+
}
|
|
1658
|
+
if (removed.length > 0) {
|
|
1659
|
+
out += ` REMOVED
|
|
1660
|
+
`;
|
|
1661
|
+
for (const entry of removed) {
|
|
1662
|
+
out += ` - ${entry.name}
|
|
1663
|
+
`;
|
|
1664
|
+
}
|
|
1665
|
+
}
|
|
1666
|
+
}
|
|
1667
|
+
if (otherFiles.length > 0) {
|
|
1668
|
+
out += `
|
|
1669
|
+
OTHER FILES (${otherFiles.length}): `;
|
|
1670
|
+
out += otherFiles.map((f) => f.path.split("/").pop()).join(", ");
|
|
1671
|
+
out += "\n";
|
|
1672
|
+
}
|
|
1673
|
+
out += nextSteps("changes", ctx);
|
|
1674
|
+
return out;
|
|
1675
|
+
}
|
|
1676
|
+
function formatKindIcon(kind) {
|
|
1677
|
+
switch (kind) {
|
|
1678
|
+
case "function":
|
|
1679
|
+
case "hook":
|
|
1680
|
+
case "component":
|
|
1681
|
+
return "+";
|
|
1682
|
+
case "type":
|
|
1683
|
+
case "enum":
|
|
1684
|
+
return "+";
|
|
1685
|
+
case "import":
|
|
1686
|
+
return "+";
|
|
1687
|
+
case "const":
|
|
1688
|
+
case "class":
|
|
1689
|
+
return "+";
|
|
1690
|
+
default:
|
|
1691
|
+
return "+";
|
|
1692
|
+
}
|
|
1693
|
+
}
|
|
1531
1694
|
|
|
1532
1695
|
// src/cache/index.ts
|
|
1533
1696
|
import {
|
|
@@ -2663,6 +2826,68 @@ async function getCommitsForFile(filePath, cwd, limit = 10) {
|
|
|
2663
2826
|
return [];
|
|
2664
2827
|
}
|
|
2665
2828
|
}
|
|
2829
|
+
function getDiffArgs(target) {
|
|
2830
|
+
switch (target) {
|
|
2831
|
+
case "staged":
|
|
2832
|
+
return ["--cached"];
|
|
2833
|
+
case "unstaged":
|
|
2834
|
+
return [];
|
|
2835
|
+
case "all":
|
|
2836
|
+
return ["HEAD"];
|
|
2837
|
+
}
|
|
2838
|
+
}
|
|
2839
|
+
function getChangedFiles(target, cwd) {
|
|
2840
|
+
if (!hasGitRepo(cwd)) return [];
|
|
2841
|
+
try {
|
|
2842
|
+
const diffArgs = getDiffArgs(target);
|
|
2843
|
+
const cmd = `git diff --name-only ${diffArgs.join(" ")}`;
|
|
2844
|
+
const output = execSync2(cmd, {
|
|
2845
|
+
cwd,
|
|
2846
|
+
encoding: "utf-8",
|
|
2847
|
+
stdio: ["pipe", "pipe", "pipe"]
|
|
2848
|
+
});
|
|
2849
|
+
return output.trim().split("\n").filter((line) => line.trim() !== "").map((line) => line.trim());
|
|
2850
|
+
} catch {
|
|
2851
|
+
return [];
|
|
2852
|
+
}
|
|
2853
|
+
}
|
|
2854
|
+
function getDiffForFile(filePath, target, cwd) {
|
|
2855
|
+
if (!hasGitRepo(cwd)) return "";
|
|
2856
|
+
try {
|
|
2857
|
+
const diffArgs = getDiffArgs(target);
|
|
2858
|
+
const cmd = `git diff -U0 ${diffArgs.join(" ")} -- "${filePath}"`;
|
|
2859
|
+
const output = execSync2(cmd, {
|
|
2860
|
+
cwd,
|
|
2861
|
+
encoding: "utf-8",
|
|
2862
|
+
stdio: ["pipe", "pipe", "pipe"]
|
|
2863
|
+
});
|
|
2864
|
+
return output;
|
|
2865
|
+
} catch {
|
|
2866
|
+
return "";
|
|
2867
|
+
}
|
|
2868
|
+
}
|
|
2869
|
+
function getDiffStats(filePath, target, cwd) {
|
|
2870
|
+
if (!hasGitRepo(cwd)) return null;
|
|
2871
|
+
try {
|
|
2872
|
+
const diffArgs = getDiffArgs(target);
|
|
2873
|
+
const cmd = `git diff --numstat ${diffArgs.join(" ")} -- "${filePath}"`;
|
|
2874
|
+
const output = execSync2(cmd, {
|
|
2875
|
+
cwd,
|
|
2876
|
+
encoding: "utf-8",
|
|
2877
|
+
stdio: ["pipe", "pipe", "pipe"]
|
|
2878
|
+
});
|
|
2879
|
+
const line = output.trim().split("\n")[0];
|
|
2880
|
+
if (!line) return null;
|
|
2881
|
+
const parts = line.split(" ");
|
|
2882
|
+
if (parts.length < 2) return null;
|
|
2883
|
+
const added = parts[0] === "-" ? 0 : parseInt(parts[0], 10);
|
|
2884
|
+
const removed = parts[1] === "-" ? 0 : parseInt(parts[1], 10);
|
|
2885
|
+
if (Number.isNaN(added) || Number.isNaN(removed)) return null;
|
|
2886
|
+
return { added, removed };
|
|
2887
|
+
} catch {
|
|
2888
|
+
return null;
|
|
2889
|
+
}
|
|
2890
|
+
}
|
|
2666
2891
|
|
|
2667
2892
|
// src/commands/impact.ts
|
|
2668
2893
|
async function impact(target, options = {}) {
|
|
@@ -3727,6 +3952,14 @@ function extractTriggerInfo(init, triggerName) {
|
|
|
3727
3952
|
}
|
|
3728
3953
|
|
|
3729
3954
|
// src/ts/cache.ts
|
|
3955
|
+
function extractJsDocDescription(node) {
|
|
3956
|
+
const descriptions = node.getJsDocs().map((d) => d.getDescription().trim()).filter(Boolean);
|
|
3957
|
+
return descriptions.length > 0 ? descriptions.join(" ") : void 0;
|
|
3958
|
+
}
|
|
3959
|
+
function extractVarStatementJsDoc(varStatement) {
|
|
3960
|
+
const descriptions = varStatement.getJsDocs().map((d) => d.getDescription().trim()).filter(Boolean);
|
|
3961
|
+
return descriptions.length > 0 ? descriptions.join(" ") : void 0;
|
|
3962
|
+
}
|
|
3730
3963
|
function indexProject(cwd) {
|
|
3731
3964
|
const resolvedCwd = cwd || process.cwd();
|
|
3732
3965
|
const allFiles = getAllCodeFiles(resolvedCwd);
|
|
@@ -3804,6 +4037,7 @@ function indexProject(cwd) {
|
|
|
3804
4037
|
const params = func.getParameters().map((p) => p.getName());
|
|
3805
4038
|
const returnType = simplifyType(safeGetReturnType(func));
|
|
3806
4039
|
const kind = inferSymbolKind(name, "function");
|
|
4040
|
+
const description = extractJsDocDescription(func);
|
|
3807
4041
|
const symbol = {
|
|
3808
4042
|
name,
|
|
3809
4043
|
file: filePath,
|
|
@@ -3812,7 +4046,8 @@ function indexProject(cwd) {
|
|
|
3812
4046
|
signature: `${isExported ? "export " : ""}${func.isAsync() ? "async " : ""}function ${name}(${params.join(", ")})`,
|
|
3813
4047
|
isExported,
|
|
3814
4048
|
params,
|
|
3815
|
-
returnType
|
|
4049
|
+
returnType,
|
|
4050
|
+
description
|
|
3816
4051
|
};
|
|
3817
4052
|
symbols.push(symbol);
|
|
3818
4053
|
if (!symbolsByName[name]) {
|
|
@@ -3840,6 +4075,7 @@ function indexProject(cwd) {
|
|
|
3840
4075
|
const params = funcLike.getParameters().map((p) => p.getName());
|
|
3841
4076
|
const returnType = simplifyType(safeGetReturnType(funcLike));
|
|
3842
4077
|
const kind = inferSymbolKind(name, "function");
|
|
4078
|
+
const description = extractVarStatementJsDoc(varStatement);
|
|
3843
4079
|
const symbol = {
|
|
3844
4080
|
name,
|
|
3845
4081
|
file: filePath,
|
|
@@ -3848,7 +4084,8 @@ function indexProject(cwd) {
|
|
|
3848
4084
|
signature: `${isExported ? "export " : ""}const ${name} = (${params.join(", ")}) => ...`,
|
|
3849
4085
|
isExported,
|
|
3850
4086
|
params,
|
|
3851
|
-
returnType
|
|
4087
|
+
returnType,
|
|
4088
|
+
description
|
|
3852
4089
|
};
|
|
3853
4090
|
symbols.push(symbol);
|
|
3854
4091
|
if (!symbolsByName[name]) {
|
|
@@ -3881,6 +4118,7 @@ function indexProject(cwd) {
|
|
|
3881
4118
|
}
|
|
3882
4119
|
if (triggerName && FIREBASE_V2_TRIGGERS.has(triggerName)) {
|
|
3883
4120
|
const triggerInfo = extractTriggerInfo(init, triggerName);
|
|
4121
|
+
const description = extractVarStatementJsDoc(varStatement);
|
|
3884
4122
|
const symbol = {
|
|
3885
4123
|
name,
|
|
3886
4124
|
file: filePath,
|
|
@@ -3888,7 +4126,8 @@ function indexProject(cwd) {
|
|
|
3888
4126
|
kind: "trigger",
|
|
3889
4127
|
signature: `${isExported ? "export " : ""}const ${name} = ${triggerName}(...)`,
|
|
3890
4128
|
isExported,
|
|
3891
|
-
triggerInfo
|
|
4129
|
+
triggerInfo,
|
|
4130
|
+
description
|
|
3892
4131
|
};
|
|
3893
4132
|
symbols.push(symbol);
|
|
3894
4133
|
if (!symbolsByName[name]) {
|
|
@@ -3901,13 +4140,15 @@ function indexProject(cwd) {
|
|
|
3901
4140
|
} else {
|
|
3902
4141
|
const declKind = varStatement.getDeclarationKind();
|
|
3903
4142
|
if (declKind.toString() === "const") {
|
|
4143
|
+
const description = extractVarStatementJsDoc(varStatement);
|
|
3904
4144
|
const symbol = {
|
|
3905
4145
|
name,
|
|
3906
4146
|
file: filePath,
|
|
3907
4147
|
line: varDecl.getStartLineNumber(),
|
|
3908
4148
|
kind: "const",
|
|
3909
4149
|
signature: `${isExported ? "export " : ""}const ${name} = ${truncateCode(init.getText(), 40)}`,
|
|
3910
|
-
isExported
|
|
4150
|
+
isExported,
|
|
4151
|
+
description
|
|
3911
4152
|
};
|
|
3912
4153
|
symbols.push(symbol);
|
|
3913
4154
|
if (!symbolsByName[name]) {
|
|
@@ -3922,13 +4163,15 @@ function indexProject(cwd) {
|
|
|
3922
4163
|
} else {
|
|
3923
4164
|
const declKind = varStatement.getDeclarationKind();
|
|
3924
4165
|
if (declKind.toString() !== "const") continue;
|
|
4166
|
+
const description = extractVarStatementJsDoc(varStatement);
|
|
3925
4167
|
const symbol = {
|
|
3926
4168
|
name,
|
|
3927
4169
|
file: filePath,
|
|
3928
4170
|
line: varDecl.getStartLineNumber(),
|
|
3929
4171
|
kind: "const",
|
|
3930
4172
|
signature: `${isExported ? "export " : ""}const ${name} = ${truncateCode(init.getText(), 40)}`,
|
|
3931
|
-
isExported
|
|
4173
|
+
isExported,
|
|
4174
|
+
description
|
|
3932
4175
|
};
|
|
3933
4176
|
symbols.push(symbol);
|
|
3934
4177
|
if (!symbolsByName[name]) {
|
|
@@ -3944,6 +4187,7 @@ function indexProject(cwd) {
|
|
|
3944
4187
|
for (const iface of sourceFile.getInterfaces()) {
|
|
3945
4188
|
const name = iface.getName();
|
|
3946
4189
|
const isExported = iface.isExported();
|
|
4190
|
+
const description = extractJsDocDescription(iface);
|
|
3947
4191
|
const symbol = {
|
|
3948
4192
|
name,
|
|
3949
4193
|
file: filePath,
|
|
@@ -3951,7 +4195,8 @@ function indexProject(cwd) {
|
|
|
3951
4195
|
kind: "interface",
|
|
3952
4196
|
signature: `${isExported ? "export " : ""}interface ${name}`,
|
|
3953
4197
|
isExported,
|
|
3954
|
-
definition: formatInterfaceDefinition(iface)
|
|
4198
|
+
definition: formatInterfaceDefinition(iface),
|
|
4199
|
+
description
|
|
3955
4200
|
};
|
|
3956
4201
|
symbols.push(symbol);
|
|
3957
4202
|
if (!symbolsByName[name]) {
|
|
@@ -3965,6 +4210,7 @@ function indexProject(cwd) {
|
|
|
3965
4210
|
for (const typeAlias of sourceFile.getTypeAliases()) {
|
|
3966
4211
|
const name = typeAlias.getName();
|
|
3967
4212
|
const isExported = typeAlias.isExported();
|
|
4213
|
+
const description = extractJsDocDescription(typeAlias);
|
|
3968
4214
|
const symbol = {
|
|
3969
4215
|
name,
|
|
3970
4216
|
file: filePath,
|
|
@@ -3972,7 +4218,8 @@ function indexProject(cwd) {
|
|
|
3972
4218
|
kind: "type",
|
|
3973
4219
|
signature: `${isExported ? "export " : ""}type ${name}`,
|
|
3974
4220
|
isExported,
|
|
3975
|
-
definition: simplifyType(safeGetTypeText(() => typeAlias.getType()))
|
|
4221
|
+
definition: simplifyType(safeGetTypeText(() => typeAlias.getType())),
|
|
4222
|
+
description
|
|
3976
4223
|
};
|
|
3977
4224
|
symbols.push(symbol);
|
|
3978
4225
|
if (!symbolsByName[name]) {
|
|
@@ -3986,6 +4233,7 @@ function indexProject(cwd) {
|
|
|
3986
4233
|
for (const enumDecl of sourceFile.getEnums()) {
|
|
3987
4234
|
const name = enumDecl.getName();
|
|
3988
4235
|
const isExported = enumDecl.isExported();
|
|
4236
|
+
const description = extractJsDocDescription(enumDecl);
|
|
3989
4237
|
const symbol = {
|
|
3990
4238
|
name,
|
|
3991
4239
|
file: filePath,
|
|
@@ -3993,7 +4241,8 @@ function indexProject(cwd) {
|
|
|
3993
4241
|
kind: "enum",
|
|
3994
4242
|
signature: `${isExported ? "export " : ""}enum ${name}`,
|
|
3995
4243
|
isExported,
|
|
3996
|
-
definition: enumDecl.getMembers().map((m) => m.getName()).join(" | ")
|
|
4244
|
+
definition: enumDecl.getMembers().map((m) => m.getName()).join(" | "),
|
|
4245
|
+
description
|
|
3997
4246
|
};
|
|
3998
4247
|
symbols.push(symbol);
|
|
3999
4248
|
if (!symbolsByName[name]) {
|
|
@@ -5264,6 +5513,478 @@ function getTriggerIcon(trigger) {
|
|
|
5264
5513
|
return "\u26A1";
|
|
5265
5514
|
}
|
|
5266
5515
|
|
|
5516
|
+
// src/commands/changes.ts
|
|
5517
|
+
async function changes(options = {}) {
|
|
5518
|
+
const { cwd, format } = parseCommandOptions(options);
|
|
5519
|
+
const ctx = options.ctx || "cli";
|
|
5520
|
+
const target = options.target || "all";
|
|
5521
|
+
if (!hasGitRepo(cwd)) {
|
|
5522
|
+
throw new Error("Nao e um repositorio Git. Execute este comando em um projeto com Git.");
|
|
5523
|
+
}
|
|
5524
|
+
try {
|
|
5525
|
+
const changedFiles = getChangedFiles(target, cwd);
|
|
5526
|
+
if (changedFiles.length === 0) {
|
|
5527
|
+
const result2 = {
|
|
5528
|
+
version: "1.0.0",
|
|
5529
|
+
timestamp: (/* @__PURE__ */ new Date()).toISOString(),
|
|
5530
|
+
target,
|
|
5531
|
+
summary: {
|
|
5532
|
+
totalFiles: 0,
|
|
5533
|
+
totalAdded: 0,
|
|
5534
|
+
totalRemoved: 0
|
|
5535
|
+
},
|
|
5536
|
+
files: []
|
|
5537
|
+
};
|
|
5538
|
+
return formatOutput(result2, format, (r) => formatChangesText(r, ctx));
|
|
5539
|
+
}
|
|
5540
|
+
const DEFAULT_IGNORE = [".analyze/"];
|
|
5541
|
+
const filesFiltered = changedFiles.filter(
|
|
5542
|
+
(f) => !DEFAULT_IGNORE.some((pattern) => f.includes(pattern))
|
|
5543
|
+
);
|
|
5544
|
+
const filesToProcess = options.file ? filesFiltered.filter(
|
|
5545
|
+
(f) => f.toLowerCase().includes(options.file.toLowerCase())
|
|
5546
|
+
) : filesFiltered;
|
|
5547
|
+
const fileChanges = [];
|
|
5548
|
+
let totalAdded = 0;
|
|
5549
|
+
let totalRemoved = 0;
|
|
5550
|
+
let symbolsIndex = null;
|
|
5551
|
+
for (const filePath of filesToProcess) {
|
|
5552
|
+
const stats = getDiffStats(filePath, target, cwd);
|
|
5553
|
+
const diffText = getDiffForFile(filePath, target, cwd);
|
|
5554
|
+
const entries = classifyChanges(diffText);
|
|
5555
|
+
if (stats) {
|
|
5556
|
+
totalAdded += stats.added;
|
|
5557
|
+
totalRemoved += stats.removed;
|
|
5558
|
+
}
|
|
5559
|
+
const enrichedEntries = enrichWithJsDoc(entries, filePath, cwd, symbolsIndex);
|
|
5560
|
+
fileChanges.push({
|
|
5561
|
+
path: filePath,
|
|
5562
|
+
category: detectCategory(filePath),
|
|
5563
|
+
stats: stats || { added: 0, removed: 0 },
|
|
5564
|
+
changes: enrichedEntries
|
|
5565
|
+
});
|
|
5566
|
+
}
|
|
5567
|
+
const affectedAreas = buildAffectedAreas(fileChanges, cwd);
|
|
5568
|
+
const totalForSummary = options.file ? fileChanges.length : changedFiles.length;
|
|
5569
|
+
const result = {
|
|
5570
|
+
version: "1.0.0",
|
|
5571
|
+
timestamp: (/* @__PURE__ */ new Date()).toISOString(),
|
|
5572
|
+
target,
|
|
5573
|
+
summary: {
|
|
5574
|
+
totalFiles: totalForSummary,
|
|
5575
|
+
totalAdded,
|
|
5576
|
+
totalRemoved
|
|
5577
|
+
},
|
|
5578
|
+
files: fileChanges,
|
|
5579
|
+
affectedAreas: affectedAreas.length > 0 ? affectedAreas : void 0
|
|
5580
|
+
};
|
|
5581
|
+
return formatOutput(result, format, (r) => formatChangesText(r, ctx));
|
|
5582
|
+
} catch (error) {
|
|
5583
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
5584
|
+
throw new Error(`Erro ao executar changes: ${message}`);
|
|
5585
|
+
}
|
|
5586
|
+
}
|
|
5587
|
+
function enrichWithJsDoc(entries, filePath, cwd, cachedIndex) {
|
|
5588
|
+
const needsEnrichment = (e) => e.kind !== "import" && e.kind !== "config" && !e.detail;
|
|
5589
|
+
if (!entries.some(needsEnrichment)) {
|
|
5590
|
+
return entries;
|
|
5591
|
+
}
|
|
5592
|
+
let index = cachedIndex;
|
|
5593
|
+
if (!index) {
|
|
5594
|
+
try {
|
|
5595
|
+
if (isCacheValid(cwd)) {
|
|
5596
|
+
index = getCachedSymbolsIndex(cwd);
|
|
5597
|
+
}
|
|
5598
|
+
if (!index) {
|
|
5599
|
+
index = indexProject(cwd);
|
|
5600
|
+
}
|
|
5601
|
+
} catch {
|
|
5602
|
+
return entries;
|
|
5603
|
+
}
|
|
5604
|
+
}
|
|
5605
|
+
return entries.map((entry) => {
|
|
5606
|
+
if (!needsEnrichment(entry)) return entry;
|
|
5607
|
+
const cached = index.symbolsByName[entry.name];
|
|
5608
|
+
if (cached) {
|
|
5609
|
+
const match = cached.find((s) => s.file === filePath);
|
|
5610
|
+
if (match?.description) {
|
|
5611
|
+
return { ...entry, detail: match.description };
|
|
5612
|
+
}
|
|
5613
|
+
}
|
|
5614
|
+
return entry;
|
|
5615
|
+
});
|
|
5616
|
+
}
|
|
5617
|
+
function buildAffectedAreas(fileChanges, cwd) {
|
|
5618
|
+
if (!configExists(cwd)) return [];
|
|
5619
|
+
try {
|
|
5620
|
+
const areaConfig = readConfig(cwd);
|
|
5621
|
+
const areaMap = /* @__PURE__ */ new Map();
|
|
5622
|
+
const ungroupedFiles = [];
|
|
5623
|
+
for (const file of fileChanges) {
|
|
5624
|
+
const areas2 = detectFileAreas(file.path, areaConfig);
|
|
5625
|
+
if (areas2.length > 0) {
|
|
5626
|
+
const areaId = areas2[0];
|
|
5627
|
+
if (!areaMap.has(areaId)) {
|
|
5628
|
+
areaMap.set(areaId, {
|
|
5629
|
+
name: getAreaName(areaId, areaConfig),
|
|
5630
|
+
description: getAreaDescription(areaId, areaConfig),
|
|
5631
|
+
files: []
|
|
5632
|
+
});
|
|
5633
|
+
}
|
|
5634
|
+
areaMap.get(areaId).files.push(file.path);
|
|
5635
|
+
} else {
|
|
5636
|
+
ungroupedFiles.push(file.path);
|
|
5637
|
+
}
|
|
5638
|
+
}
|
|
5639
|
+
const affectedAreas = [];
|
|
5640
|
+
for (const [id, info] of areaMap) {
|
|
5641
|
+
affectedAreas.push({
|
|
5642
|
+
id,
|
|
5643
|
+
name: info.name,
|
|
5644
|
+
description: info.description,
|
|
5645
|
+
fileCount: info.files.length,
|
|
5646
|
+
files: info.files
|
|
5647
|
+
});
|
|
5648
|
+
}
|
|
5649
|
+
if (ungroupedFiles.length > 0) {
|
|
5650
|
+
affectedAreas.push({
|
|
5651
|
+
id: "ungrouped",
|
|
5652
|
+
name: "ungrouped",
|
|
5653
|
+
fileCount: ungroupedFiles.length,
|
|
5654
|
+
files: ungroupedFiles
|
|
5655
|
+
});
|
|
5656
|
+
}
|
|
5657
|
+
return affectedAreas;
|
|
5658
|
+
} catch {
|
|
5659
|
+
return [];
|
|
5660
|
+
}
|
|
5661
|
+
}
|
|
5662
|
+
function classifyChanges(diffText) {
|
|
5663
|
+
if (!diffText.trim()) return [];
|
|
5664
|
+
const lines = diffText.split("\n");
|
|
5665
|
+
const entries = [];
|
|
5666
|
+
const relevantLines = [];
|
|
5667
|
+
let lineNumber = 0;
|
|
5668
|
+
for (const rawLine of lines) {
|
|
5669
|
+
lineNumber++;
|
|
5670
|
+
if (rawLine.startsWith("@@") || rawLine.startsWith("diff ") || rawLine.startsWith("index ") || rawLine.startsWith("---") || rawLine.startsWith("+++")) {
|
|
5671
|
+
continue;
|
|
5672
|
+
}
|
|
5673
|
+
if (rawLine.trim() === "") continue;
|
|
5674
|
+
if (rawLine.startsWith("+") || rawLine.startsWith("-")) {
|
|
5675
|
+
const content = rawLine.slice(1).trim();
|
|
5676
|
+
if (content.startsWith("//") || content.startsWith("*") || content.startsWith("/*")) {
|
|
5677
|
+
continue;
|
|
5678
|
+
}
|
|
5679
|
+
}
|
|
5680
|
+
if (rawLine.startsWith("+") || rawLine.startsWith("-")) {
|
|
5681
|
+
const content = rawLine.slice(1).trim();
|
|
5682
|
+
if (/^[})\];,]$/.test(content)) {
|
|
5683
|
+
continue;
|
|
5684
|
+
}
|
|
5685
|
+
}
|
|
5686
|
+
if (rawLine.startsWith("+")) {
|
|
5687
|
+
relevantLines.push({ prefix: "add", content: rawLine.slice(1), line: lineNumber });
|
|
5688
|
+
} else if (rawLine.startsWith("-")) {
|
|
5689
|
+
relevantLines.push({ prefix: "remove", content: rawLine.slice(1), line: lineNumber });
|
|
5690
|
+
}
|
|
5691
|
+
}
|
|
5692
|
+
const processed = /* @__PURE__ */ new Set();
|
|
5693
|
+
const removedSymbols = /* @__PURE__ */ new Map();
|
|
5694
|
+
const addedSymbols = /* @__PURE__ */ new Map();
|
|
5695
|
+
for (let i = 0; i < relevantLines.length; i++) {
|
|
5696
|
+
const line = relevantLines[i];
|
|
5697
|
+
const multiReexport = extractMultipleReexports(line.content);
|
|
5698
|
+
if (multiReexport) {
|
|
5699
|
+
for (const name of multiReexport.names) {
|
|
5700
|
+
if (line.prefix === "remove") {
|
|
5701
|
+
if (!removedSymbols.has(name)) {
|
|
5702
|
+
removedSymbols.set(name, line);
|
|
5703
|
+
}
|
|
5704
|
+
} else {
|
|
5705
|
+
if (!addedSymbols.has(name)) {
|
|
5706
|
+
addedSymbols.set(name, line);
|
|
5707
|
+
}
|
|
5708
|
+
}
|
|
5709
|
+
}
|
|
5710
|
+
continue;
|
|
5711
|
+
}
|
|
5712
|
+
const info = extractSymbolInfo(line.content);
|
|
5713
|
+
if (info && info.name && isRelevantSymbol(info)) {
|
|
5714
|
+
if (line.prefix === "remove") {
|
|
5715
|
+
if (!removedSymbols.has(info.name)) {
|
|
5716
|
+
removedSymbols.set(info.name, line);
|
|
5717
|
+
}
|
|
5718
|
+
} else {
|
|
5719
|
+
if (!addedSymbols.has(info.name)) {
|
|
5720
|
+
addedSymbols.set(info.name, line);
|
|
5721
|
+
}
|
|
5722
|
+
}
|
|
5723
|
+
}
|
|
5724
|
+
}
|
|
5725
|
+
const modifiedNames = /* @__PURE__ */ new Set();
|
|
5726
|
+
for (const name of removedSymbols.keys()) {
|
|
5727
|
+
if (addedSymbols.has(name)) {
|
|
5728
|
+
modifiedNames.add(name);
|
|
5729
|
+
}
|
|
5730
|
+
}
|
|
5731
|
+
for (const [name, line] of removedSymbols) {
|
|
5732
|
+
if (!modifiedNames.has(name)) {
|
|
5733
|
+
const info = extractSymbolInfo(line.content);
|
|
5734
|
+
if (info) {
|
|
5735
|
+
processed.add(line.line);
|
|
5736
|
+
entries.push({
|
|
5737
|
+
kind: info.kind,
|
|
5738
|
+
type: "removed",
|
|
5739
|
+
name
|
|
5740
|
+
});
|
|
5741
|
+
}
|
|
5742
|
+
}
|
|
5743
|
+
}
|
|
5744
|
+
for (const name of modifiedNames) {
|
|
5745
|
+
const removedLine = removedSymbols.get(name);
|
|
5746
|
+
const addedLine = addedSymbols.get(name);
|
|
5747
|
+
const removedInfo = extractSymbolInfo(removedLine.content);
|
|
5748
|
+
const addedInfo = extractSymbolInfo(addedLine.content);
|
|
5749
|
+
if (removedInfo && addedInfo) {
|
|
5750
|
+
processed.add(removedLine.line);
|
|
5751
|
+
processed.add(addedLine.line);
|
|
5752
|
+
const detail = generateModifiedDetail(removedLine.content, addedLine.content, addedInfo.kind);
|
|
5753
|
+
entries.push({
|
|
5754
|
+
kind: addedInfo.kind,
|
|
5755
|
+
type: "modified",
|
|
5756
|
+
name,
|
|
5757
|
+
detail
|
|
5758
|
+
});
|
|
5759
|
+
}
|
|
5760
|
+
}
|
|
5761
|
+
for (const [name, line] of addedSymbols) {
|
|
5762
|
+
if (!modifiedNames.has(name) && !processed.has(line.line)) {
|
|
5763
|
+
const info = extractSymbolInfo(line.content);
|
|
5764
|
+
if (info) {
|
|
5765
|
+
processed.add(line.line);
|
|
5766
|
+
entries.push({
|
|
5767
|
+
kind: info.kind,
|
|
5768
|
+
type: "added",
|
|
5769
|
+
name
|
|
5770
|
+
});
|
|
5771
|
+
}
|
|
5772
|
+
}
|
|
5773
|
+
}
|
|
5774
|
+
return entries;
|
|
5775
|
+
}
|
|
5776
|
+
var COMMON_PROPERTIES = /* @__PURE__ */ new Set([
|
|
5777
|
+
"inputSchema",
|
|
5778
|
+
"outputSchema",
|
|
5779
|
+
"annotations",
|
|
5780
|
+
"content",
|
|
5781
|
+
"isError",
|
|
5782
|
+
"description",
|
|
5783
|
+
"title",
|
|
5784
|
+
"name",
|
|
5785
|
+
"version",
|
|
5786
|
+
"type",
|
|
5787
|
+
"properties",
|
|
5788
|
+
"required",
|
|
5789
|
+
"stats",
|
|
5790
|
+
"summary",
|
|
5791
|
+
"totalFiles",
|
|
5792
|
+
"totalAdded",
|
|
5793
|
+
"totalRemoved",
|
|
5794
|
+
"options",
|
|
5795
|
+
"params",
|
|
5796
|
+
"args",
|
|
5797
|
+
"config",
|
|
5798
|
+
"data",
|
|
5799
|
+
"result",
|
|
5800
|
+
"error",
|
|
5801
|
+
"format",
|
|
5802
|
+
"cwd",
|
|
5803
|
+
"target",
|
|
5804
|
+
"file",
|
|
5805
|
+
"cache",
|
|
5806
|
+
"ctx",
|
|
5807
|
+
"value",
|
|
5808
|
+
"label",
|
|
5809
|
+
"default",
|
|
5810
|
+
"enum",
|
|
5811
|
+
"items",
|
|
5812
|
+
"stdio",
|
|
5813
|
+
"encoding",
|
|
5814
|
+
"actions",
|
|
5815
|
+
"reducer",
|
|
5816
|
+
"payload",
|
|
5817
|
+
"state",
|
|
5818
|
+
"children",
|
|
5819
|
+
"className",
|
|
5820
|
+
"sx",
|
|
5821
|
+
"theme"
|
|
5822
|
+
]);
|
|
5823
|
+
function extractMultipleReexports(content) {
|
|
5824
|
+
const trimmed = content.trim();
|
|
5825
|
+
const typeMatch = trimmed.match(
|
|
5826
|
+
/export\s+type\s+\{([^}]+)\}\s*from\s+['"][^'"]+['"]/
|
|
5827
|
+
);
|
|
5828
|
+
if (typeMatch) {
|
|
5829
|
+
const names = typeMatch[1].split(",").map((n) => n.trim()).filter(Boolean);
|
|
5830
|
+
return names.length > 1 ? { names, kind: "type" } : null;
|
|
5831
|
+
}
|
|
5832
|
+
const valueMatch = trimmed.match(
|
|
5833
|
+
/export\s+\{([^}]+)\}\s*from\s+['"][^'"]+['"]/
|
|
5834
|
+
);
|
|
5835
|
+
if (valueMatch) {
|
|
5836
|
+
const names = valueMatch[1].split(",").map((n) => n.trim()).filter(Boolean);
|
|
5837
|
+
return names.length > 1 ? { names, kind: "import" } : null;
|
|
5838
|
+
}
|
|
5839
|
+
const localTypeMatch = trimmed.match(/export\s+type\s+\{([^}]+)\}/);
|
|
5840
|
+
if (localTypeMatch) {
|
|
5841
|
+
const names = localTypeMatch[1].split(",").map((n) => n.trim()).filter(Boolean);
|
|
5842
|
+
return names.length > 1 ? { names, kind: "type" } : null;
|
|
5843
|
+
}
|
|
5844
|
+
return null;
|
|
5845
|
+
}
|
|
5846
|
+
function isRelevantSymbol(info) {
|
|
5847
|
+
if (info.kind === "import" || info.kind === "type" || info.kind === "enum" || info.kind === "class" || info.kind === "config") {
|
|
5848
|
+
return true;
|
|
5849
|
+
}
|
|
5850
|
+
if (info.kind === "function" || info.kind === "hook" || info.kind === "component") {
|
|
5851
|
+
return true;
|
|
5852
|
+
}
|
|
5853
|
+
if (info.isExported) {
|
|
5854
|
+
return true;
|
|
5855
|
+
}
|
|
5856
|
+
if (info.kind === "const") {
|
|
5857
|
+
if (/^[A-Z_]+$/.test(info.name) && info.name.length > 2) {
|
|
5858
|
+
return true;
|
|
5859
|
+
}
|
|
5860
|
+
if (/^[A-Z]/.test(info.name)) {
|
|
5861
|
+
return true;
|
|
5862
|
+
}
|
|
5863
|
+
if (info.name.startsWith("use") && info.name.length > 3) {
|
|
5864
|
+
return true;
|
|
5865
|
+
}
|
|
5866
|
+
return false;
|
|
5867
|
+
}
|
|
5868
|
+
return true;
|
|
5869
|
+
}
|
|
5870
|
+
function extractSymbolInfo(content) {
|
|
5871
|
+
const trimmed = content.trim();
|
|
5872
|
+
const importMatch = trimmed.match(/import\s+.*from\s+['"](.+)['"]/);
|
|
5873
|
+
if (importMatch) {
|
|
5874
|
+
return { name: importMatch[1], kind: "import", isExported: false };
|
|
5875
|
+
}
|
|
5876
|
+
const reExportTypeMatch = trimmed.match(/export\s+type\s+\{([^}]+)\}\s*from\s+['"]([^'"]+)['"]/);
|
|
5877
|
+
if (reExportTypeMatch) {
|
|
5878
|
+
const names = reExportTypeMatch[1].split(",").map((n) => n.trim()).filter(Boolean);
|
|
5879
|
+
const firstName = names[0] ?? reExportTypeMatch[2];
|
|
5880
|
+
const fromPath = reExportTypeMatch[2];
|
|
5881
|
+
return { name: `${firstName} (from ${fromPath})`, kind: "type", isExported: true };
|
|
5882
|
+
}
|
|
5883
|
+
const reExportMatch = trimmed.match(/export\s+\{([^}]+)\}\s*from\s+['"]([^'"]+)['"]/);
|
|
5884
|
+
if (reExportMatch) {
|
|
5885
|
+
const names = reExportMatch[1].split(",").map((n) => n.trim()).filter(Boolean);
|
|
5886
|
+
const firstName = names[0] ?? reExportMatch[2];
|
|
5887
|
+
const fromPath = reExportMatch[2];
|
|
5888
|
+
return { name: `${firstName} (from ${fromPath})`, kind: "import", isExported: true };
|
|
5889
|
+
}
|
|
5890
|
+
const funcMatch = trimmed.match(/(?:export\s+)?function\s+(\w+)/);
|
|
5891
|
+
if (funcMatch) {
|
|
5892
|
+
const isExported = /^export\s+/.test(trimmed);
|
|
5893
|
+
const kind = detectFunctionKind(funcMatch[1], trimmed);
|
|
5894
|
+
return { name: funcMatch[1], kind, isExported };
|
|
5895
|
+
}
|
|
5896
|
+
const constMatch = trimmed.match(/(?:export\s+)?const\s+(\w+)\s*[=:(]/);
|
|
5897
|
+
if (constMatch) {
|
|
5898
|
+
const isExported = /^export\s+/.test(trimmed);
|
|
5899
|
+
const kind = detectConstKind(constMatch[1]);
|
|
5900
|
+
return { name: constMatch[1], kind, isExported };
|
|
5901
|
+
}
|
|
5902
|
+
const typeMatch = trimmed.match(/(?:export\s+)?(interface|type|enum)\s+(\w+)/);
|
|
5903
|
+
if (typeMatch) {
|
|
5904
|
+
const isExported = /^export\s+/.test(trimmed);
|
|
5905
|
+
const kindMap = {
|
|
5906
|
+
interface: "type",
|
|
5907
|
+
type: "type",
|
|
5908
|
+
enum: "enum"
|
|
5909
|
+
};
|
|
5910
|
+
return { name: typeMatch[2], kind: kindMap[typeMatch[1]], isExported };
|
|
5911
|
+
}
|
|
5912
|
+
const classMatch = trimmed.match(/(?:export\s+)?class\s+(\w+)/);
|
|
5913
|
+
if (classMatch) {
|
|
5914
|
+
const isExported = /^export\s+/.test(trimmed);
|
|
5915
|
+
return { name: classMatch[1], kind: "class", isExported };
|
|
5916
|
+
}
|
|
5917
|
+
const entryMatchFull = content.match(/^(\s{2,})(\w[\w-]*)\s*:\s*[{[]/);
|
|
5918
|
+
if (entryMatchFull && !COMMON_PROPERTIES.has(entryMatchFull[2])) {
|
|
5919
|
+
return { name: entryMatchFull[2], kind: "config", isExported: false };
|
|
5920
|
+
}
|
|
5921
|
+
return null;
|
|
5922
|
+
}
|
|
5923
|
+
function detectFunctionKind(name, content) {
|
|
5924
|
+
if (name.startsWith("use") && name.length > 3) {
|
|
5925
|
+
return "hook";
|
|
5926
|
+
}
|
|
5927
|
+
if (/^[A-Z]/.test(name) && content.includes("(")) {
|
|
5928
|
+
return "component";
|
|
5929
|
+
}
|
|
5930
|
+
return "function";
|
|
5931
|
+
}
|
|
5932
|
+
function detectConstKind(name) {
|
|
5933
|
+
if (name.startsWith("use") && name.length > 3) {
|
|
5934
|
+
return "hook";
|
|
5935
|
+
}
|
|
5936
|
+
if (/^[A-Z][A-Z_]+$/.test(name) && name.length > 1) {
|
|
5937
|
+
return "const";
|
|
5938
|
+
}
|
|
5939
|
+
if (/^[A-Z]/.test(name)) {
|
|
5940
|
+
return "component";
|
|
5941
|
+
}
|
|
5942
|
+
return "const";
|
|
5943
|
+
}
|
|
5944
|
+
function generateModifiedDetail(oldLine, newLine, kind) {
|
|
5945
|
+
const details = [];
|
|
5946
|
+
const oldReturnMatch = oldLine.match(/:\s*(Promise<)?([A-Z]\w+)(\)?>?)\s*$/);
|
|
5947
|
+
const newReturnMatch = newLine.match(/:\s*(Promise<)?([A-Z]\w+)(\)?>?)\s*$/);
|
|
5948
|
+
if (oldReturnMatch && newReturnMatch && oldReturnMatch[0] !== newReturnMatch[0]) {
|
|
5949
|
+
const oldType = oldReturnMatch[0].trim();
|
|
5950
|
+
const newType = newReturnMatch[0].trim();
|
|
5951
|
+
details.push(`return type: ${oldType} -> ${newType}`);
|
|
5952
|
+
}
|
|
5953
|
+
const oldParams = extractParams2(oldLine);
|
|
5954
|
+
const newParams = extractParams2(newLine);
|
|
5955
|
+
if (oldParams !== newParams && oldParams && newParams) {
|
|
5956
|
+
if (oldParams.split(",").length !== newParams.split(",").length) {
|
|
5957
|
+
details.push("params changed");
|
|
5958
|
+
}
|
|
5959
|
+
}
|
|
5960
|
+
const wasAsync = /^\s*async\s+/.test(oldLine) || /export\s+async\s+/.test(oldLine);
|
|
5961
|
+
const isAsync = /^\s*async\s+/.test(newLine) || /export\s+async\s+/.test(newLine);
|
|
5962
|
+
if (!wasAsync && isAsync) {
|
|
5963
|
+
details.push("now async");
|
|
5964
|
+
} else if (wasAsync && !isAsync) {
|
|
5965
|
+
details.push("no longer async");
|
|
5966
|
+
}
|
|
5967
|
+
if (kind === "type") {
|
|
5968
|
+
const oldBody = extractTypeBody(oldLine);
|
|
5969
|
+
const newBody = extractTypeBody(newLine);
|
|
5970
|
+
if (oldBody !== newBody) {
|
|
5971
|
+
details.push("definition changed");
|
|
5972
|
+
}
|
|
5973
|
+
}
|
|
5974
|
+
if (details.length === 0) {
|
|
5975
|
+
details.push("implementation changed");
|
|
5976
|
+
}
|
|
5977
|
+
return details.join(", ");
|
|
5978
|
+
}
|
|
5979
|
+
function extractParams2(line) {
|
|
5980
|
+
const match = line.match(/\(([^)]*)\)/);
|
|
5981
|
+
return match ? match[1].trim() : null;
|
|
5982
|
+
}
|
|
5983
|
+
function extractTypeBody(line) {
|
|
5984
|
+
const eqIndex = line.indexOf("=");
|
|
5985
|
+
return eqIndex >= 0 ? line.slice(eqIndex + 1).trim() : line.trim();
|
|
5986
|
+
}
|
|
5987
|
+
|
|
5267
5988
|
// src/commands/find.ts
|
|
5268
5989
|
import { readFileSync as readFileSync4 } from "fs";
|
|
5269
5990
|
import { join as join11 } from "path";
|
|
@@ -5592,5 +6313,6 @@ export {
|
|
|
5592
6313
|
areasInit,
|
|
5593
6314
|
find,
|
|
5594
6315
|
functions,
|
|
6316
|
+
changes,
|
|
5595
6317
|
VERSION
|
|
5596
6318
|
};
|
package/dist/cli.js
CHANGED
|
@@ -1,13 +1,14 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
import {
|
|
3
3
|
describe
|
|
4
|
-
} from "./chunk-
|
|
4
|
+
} from "./chunk-XVOQNZHC.js";
|
|
5
5
|
import {
|
|
6
6
|
VERSION,
|
|
7
7
|
area,
|
|
8
8
|
areaContext,
|
|
9
9
|
areas,
|
|
10
10
|
areasInit,
|
|
11
|
+
changes,
|
|
11
12
|
context,
|
|
12
13
|
dead,
|
|
13
14
|
deadFix,
|
|
@@ -16,7 +17,7 @@ import {
|
|
|
16
17
|
impact,
|
|
17
18
|
map,
|
|
18
19
|
suggest
|
|
19
|
-
} from "./chunk-
|
|
20
|
+
} from "./chunk-XFXRWLQA.js";
|
|
20
21
|
|
|
21
22
|
// src/cli.ts
|
|
22
23
|
import { resolve } from "path";
|
|
@@ -55,6 +56,12 @@ FIREBASE:
|
|
|
55
56
|
functions Lista todas as Cloud Functions do projeto
|
|
56
57
|
functions --trigger=onCall Filtra por tipo de trigger
|
|
57
58
|
|
|
59
|
+
CHANGES:
|
|
60
|
+
changes Resumo semantico de mudancas git
|
|
61
|
+
changes --staged Mudancas no staging area (git add)
|
|
62
|
+
changes --unstaged Mudancas no working directory
|
|
63
|
+
changes --file=src/types/auth.ts Detalhar um arquivo especifico
|
|
64
|
+
|
|
58
65
|
MODOS:
|
|
59
66
|
--mcp Inicia servidor MCP para integracao com Claude Desktop
|
|
60
67
|
|
|
@@ -115,7 +122,7 @@ async function main() {
|
|
|
115
122
|
}
|
|
116
123
|
}
|
|
117
124
|
if (flags.mcp) {
|
|
118
|
-
const { startMcpServer } = await import("./server-
|
|
125
|
+
const { startMcpServer } = await import("./server-M2RWCDVQ.js");
|
|
119
126
|
await startMcpServer();
|
|
120
127
|
return;
|
|
121
128
|
}
|
|
@@ -238,6 +245,14 @@ async function main() {
|
|
|
238
245
|
}
|
|
239
246
|
result = await describe(target, { format, cwd });
|
|
240
247
|
break;
|
|
248
|
+
case "changes":
|
|
249
|
+
result = await changes({
|
|
250
|
+
format,
|
|
251
|
+
cwd,
|
|
252
|
+
target: flags.staged ? "staged" : flags.unstaged ? "unstaged" : "all",
|
|
253
|
+
file: flags.file
|
|
254
|
+
});
|
|
255
|
+
break;
|
|
241
256
|
default:
|
|
242
257
|
console.error(`\u274C Comando desconhecido: ${command}`);
|
|
243
258
|
console.error(" Use 'ai-tool --help' para ver comandos dispon\xEDveis.");
|
package/dist/index.d.ts
CHANGED
|
@@ -288,6 +288,47 @@ interface AreaContextResult {
|
|
|
288
288
|
stores: AreaContextStoreInfo[];
|
|
289
289
|
triggers: AreaContextTriggerInfo[];
|
|
290
290
|
}
|
|
291
|
+
type ChangeType = "added" | "modified" | "removed";
|
|
292
|
+
type ChangeKind = "function" | "type" | "import" | "const" | "component" | "hook" | "enum" | "class" | "config" | "other";
|
|
293
|
+
interface ChangeEntry {
|
|
294
|
+
kind: ChangeKind;
|
|
295
|
+
type: ChangeType;
|
|
296
|
+
name: string;
|
|
297
|
+
detail?: string;
|
|
298
|
+
line?: number;
|
|
299
|
+
}
|
|
300
|
+
interface FileChange {
|
|
301
|
+
path: string;
|
|
302
|
+
category: FileCategory;
|
|
303
|
+
stats: {
|
|
304
|
+
added: number;
|
|
305
|
+
removed: number;
|
|
306
|
+
};
|
|
307
|
+
changes: ChangeEntry[];
|
|
308
|
+
}
|
|
309
|
+
interface ChangesOptions extends CommandOptions {
|
|
310
|
+
target?: "staged" | "unstaged" | "all";
|
|
311
|
+
file?: string;
|
|
312
|
+
}
|
|
313
|
+
interface AffectedArea {
|
|
314
|
+
id: string;
|
|
315
|
+
name: string;
|
|
316
|
+
description?: string;
|
|
317
|
+
fileCount: number;
|
|
318
|
+
files: string[];
|
|
319
|
+
}
|
|
320
|
+
interface ChangesResult {
|
|
321
|
+
version: string;
|
|
322
|
+
timestamp: string;
|
|
323
|
+
target: "staged" | "unstaged" | "all";
|
|
324
|
+
summary: {
|
|
325
|
+
totalFiles: number;
|
|
326
|
+
totalAdded: number;
|
|
327
|
+
totalRemoved: number;
|
|
328
|
+
};
|
|
329
|
+
files: FileChange[];
|
|
330
|
+
affectedAreas?: AffectedArea[];
|
|
331
|
+
}
|
|
291
332
|
|
|
292
333
|
/**
|
|
293
334
|
* Comando MAP - Mapa do projeto usando Skott
|
|
@@ -469,6 +510,22 @@ interface FunctionsOptions {
|
|
|
469
510
|
*/
|
|
470
511
|
declare function functions(options?: FunctionsOptions): Promise<string>;
|
|
471
512
|
|
|
513
|
+
/**
|
|
514
|
+
* Comando CHANGES - Resumo semantico de mudancas git
|
|
515
|
+
*
|
|
516
|
+
* Analisa arquivos modificados via git diff, classifica mudancas por tipo
|
|
517
|
+
* (added/modified/removed) e categoria (function/type/import/const/component/hook)
|
|
518
|
+
* usando pattern matching deterministico, e retorna um resumo estruturado.
|
|
519
|
+
*/
|
|
520
|
+
|
|
521
|
+
/**
|
|
522
|
+
* Executa o comando CHANGES
|
|
523
|
+
*
|
|
524
|
+
* Retorna um resumo semantico de mudancas git otimizado para consumo de IA.
|
|
525
|
+
* Ao inves de mostrar diff bruto, classifica mudancas por tipo e categoria.
|
|
526
|
+
*/
|
|
527
|
+
declare function changes(options?: ChangesOptions): Promise<string>;
|
|
528
|
+
|
|
472
529
|
/**
|
|
473
530
|
* Comando FIND - Busca símbolos no código usando índice cacheado
|
|
474
531
|
*
|
|
@@ -587,6 +644,8 @@ interface SymbolInfo {
|
|
|
587
644
|
definition?: string;
|
|
588
645
|
/** Metadados específicos para triggers Firebase */
|
|
589
646
|
triggerInfo?: TriggerInfo;
|
|
647
|
+
/** Descrição extraída do JSDoc */
|
|
648
|
+
description?: string;
|
|
590
649
|
}
|
|
591
650
|
/**
|
|
592
651
|
* Informação de import
|
|
@@ -936,4 +995,4 @@ declare function inferFileDescription(filePath: string, category: string): strin
|
|
|
936
995
|
|
|
937
996
|
declare const VERSION: string;
|
|
938
997
|
|
|
939
|
-
export { type AreaConfig, type AreaContextComponentInfo, type AreaContextFunctionInfo, type AreaContextResult, type AreaContextStoreInfo, type AreaContextTriggerInfo, type AreaContextTypeInfo, type AreaDetailResult, type AreaFile, type AreaInfo, type AreaOptions, type AreasConfigFile, type AreasOptions, type AreasResult, type CloudFunctionInfo, type CommandOptions, type ContextOptions, type ContextResult, type DeadFile, type DeadOptions, type DeadResult, type DetectedArea, type FileCategory, type FileInfo, type FindMatch, type FindOptions, type FindResult, type FindSimilarOptions, type FolderStats, type FormatAreaNotFoundOptions, type FormatFileNotFoundOptions, type FunctionInfo, type FunctionsOptions, type FunctionsResult, type HintContext, type ImpactFile, type ImpactOptions, type ImpactResult, type ImportInfo$1 as ImportInfo, type MapOptions, type MapResult, type OutputFormat, type ParamInfo, type ProjectIndex, type RecoveryErrorType, type RiskInfo, type SuggestOptions, type SuggestResult, type Suggestion, type SuggestionPriority, type SymbolInfo, type SymbolType, type TriggerInfo, type TypeInfo, type TypeKind, VERSION, area, areas, areasInit, categoryIcons, clearFirebaseCache, configExists, context, dead, deadFix, detectCategory, detectFileAreas, extractFileName, filterCloudFunctionsFalsePositives, find, findBestMatch, findSimilar, formatAreaNotFound, formatFileNotFound, formatInvalidCommand, formatMissingTarget, functions, getAreaDescription, getAreaName, getCacheDir, getFileDescription, hasFirebaseFunctions, hint, impact, inferFileDescription, invalidateCache, isCacheValid, isCodeFile, isEntryPoint, isExportedCloudFunction, isFileIgnored, isFirebaseProject, levenshteinDistance, map, nextSteps, readConfig, recoveryHint, removeArea, setArea, setFileDescription, suggest, writeConfig };
|
|
998
|
+
export { type AffectedArea, type AreaConfig, type AreaContextComponentInfo, type AreaContextFunctionInfo, type AreaContextResult, type AreaContextStoreInfo, type AreaContextTriggerInfo, type AreaContextTypeInfo, type AreaDetailResult, type AreaFile, type AreaInfo, type AreaOptions, type AreasConfigFile, type AreasOptions, type AreasResult, type ChangeEntry, type ChangeKind, type ChangeType, type ChangesOptions, type ChangesResult, type CloudFunctionInfo, type CommandOptions, type ContextOptions, type ContextResult, type DeadFile, type DeadOptions, type DeadResult, type DetectedArea, type FileCategory, type FileChange, type FileInfo, type FindMatch, type FindOptions, type FindResult, type FindSimilarOptions, type FolderStats, type FormatAreaNotFoundOptions, type FormatFileNotFoundOptions, type FunctionInfo, type FunctionsOptions, type FunctionsResult, type HintContext, type ImpactFile, type ImpactOptions, type ImpactResult, type ImportInfo$1 as ImportInfo, type MapOptions, type MapResult, type OutputFormat, type ParamInfo, type ProjectIndex, type RecoveryErrorType, type RiskInfo, type SuggestOptions, type SuggestResult, type Suggestion, type SuggestionPriority, type SymbolInfo, type SymbolType, type TriggerInfo, type TypeInfo, type TypeKind, VERSION, area, areas, areasInit, categoryIcons, changes, clearFirebaseCache, configExists, context, dead, deadFix, detectCategory, detectFileAreas, extractFileName, filterCloudFunctionsFalsePositives, find, findBestMatch, findSimilar, formatAreaNotFound, formatFileNotFound, formatInvalidCommand, formatMissingTarget, functions, getAreaDescription, getAreaName, getCacheDir, getFileDescription, hasFirebaseFunctions, hint, impact, inferFileDescription, invalidateCache, isCacheValid, isCodeFile, isEntryPoint, isExportedCloudFunction, isFileIgnored, isFirebaseProject, levenshteinDistance, map, nextSteps, readConfig, recoveryHint, removeArea, setArea, setFileDescription, suggest, writeConfig };
|
package/dist/index.js
CHANGED
|
@@ -4,6 +4,7 @@ import {
|
|
|
4
4
|
areas,
|
|
5
5
|
areasInit,
|
|
6
6
|
categoryIcons,
|
|
7
|
+
changes,
|
|
7
8
|
clearFirebaseCache,
|
|
8
9
|
configExists,
|
|
9
10
|
context,
|
|
@@ -46,13 +47,14 @@ import {
|
|
|
46
47
|
setFileDescription,
|
|
47
48
|
suggest,
|
|
48
49
|
writeConfig
|
|
49
|
-
} from "./chunk-
|
|
50
|
+
} from "./chunk-XFXRWLQA.js";
|
|
50
51
|
export {
|
|
51
52
|
VERSION,
|
|
52
53
|
area,
|
|
53
54
|
areas,
|
|
54
55
|
areasInit,
|
|
55
56
|
categoryIcons,
|
|
57
|
+
changes,
|
|
56
58
|
clearFirebaseCache,
|
|
57
59
|
configExists,
|
|
58
60
|
context,
|
|
@@ -1,12 +1,13 @@
|
|
|
1
1
|
import {
|
|
2
2
|
describe
|
|
3
|
-
} from "./chunk-
|
|
3
|
+
} from "./chunk-XVOQNZHC.js";
|
|
4
4
|
import {
|
|
5
5
|
VERSION,
|
|
6
6
|
area,
|
|
7
7
|
areaContext,
|
|
8
8
|
areas,
|
|
9
9
|
areasInit,
|
|
10
|
+
changes,
|
|
10
11
|
context,
|
|
11
12
|
dead,
|
|
12
13
|
find,
|
|
@@ -15,7 +16,7 @@ import {
|
|
|
15
16
|
map,
|
|
16
17
|
recoveryHint,
|
|
17
18
|
suggest
|
|
18
|
-
} from "./chunk-
|
|
19
|
+
} from "./chunk-XFXRWLQA.js";
|
|
19
20
|
|
|
20
21
|
// src/mcp/server.ts
|
|
21
22
|
import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
|
|
@@ -543,6 +544,56 @@ Se nao encontrar: tente termos diferentes, use list_areas para ver areas disponi
|
|
|
543
544
|
const hint = recoveryHint("generic", "mcp");
|
|
544
545
|
return {
|
|
545
546
|
content: [{ type: "text", text: `Erro ao executar describe: ${msg}
|
|
547
|
+
${hint}` }],
|
|
548
|
+
isError: true
|
|
549
|
+
};
|
|
550
|
+
}
|
|
551
|
+
}
|
|
552
|
+
);
|
|
553
|
+
server2.registerTool(
|
|
554
|
+
"aitool_changes",
|
|
555
|
+
{
|
|
556
|
+
title: "Changes Summary",
|
|
557
|
+
description: `Resumo semantico de mudancas git - alternativa inteligente ao git diff.
|
|
558
|
+
Classifica mudancas por tipo (funcoes adicionadas, tipos modificados, imports removidos) usando
|
|
559
|
+
pattern matching deterministico + JSDoc dos simbolos. Inclui agrupamento por area quando configurado.
|
|
560
|
+
|
|
561
|
+
Quando usar: ANTES de implementar algo apos mudancas, entender o que mudou no projeto, revisar PRs,
|
|
562
|
+
contextualizar-se rapidamente sem gastar tokens com diff bruto. Ideal apos git add/commit.
|
|
563
|
+
NAO use para: ver diff detalhado com contexto de linhas (use git diff direto), entender dependencias
|
|
564
|
+
entre arquivos (use impact_analysis), ou listar todos os arquivos do projeto (use project_map).
|
|
565
|
+
|
|
566
|
+
Workflow: changes \u2192 context (entender tipos atuais de um arquivo) \u2192 implementar
|
|
567
|
+
Dica: use --file=<path> para focar em um arquivo especifico quando houver muitas mudancas.`,
|
|
568
|
+
inputSchema: {
|
|
569
|
+
target: z.enum(["staged", "unstaged", "all"]).default("all").describe("O que comparar: staged (git add), unstaged (working dir) ou all"),
|
|
570
|
+
file: z.string().optional().describe("Detalhar apenas um arquivo especifico (caminho parcial ou completo)"),
|
|
571
|
+
format: z.enum(["text", "json"]).default("text").describe("Formato de saida: text ou json"),
|
|
572
|
+
cwd: z.string().optional().describe("Diretorio do projeto a analisar")
|
|
573
|
+
},
|
|
574
|
+
annotations: {
|
|
575
|
+
title: "Changes Summary",
|
|
576
|
+
readOnlyHint: true,
|
|
577
|
+
destructiveHint: false,
|
|
578
|
+
idempotentHint: true,
|
|
579
|
+
openWorldHint: false
|
|
580
|
+
}
|
|
581
|
+
},
|
|
582
|
+
async (params) => {
|
|
583
|
+
try {
|
|
584
|
+
const result = await changes({
|
|
585
|
+
format: params.format,
|
|
586
|
+
cwd: params.cwd,
|
|
587
|
+
target: params.target,
|
|
588
|
+
file: params.file,
|
|
589
|
+
ctx: "mcp"
|
|
590
|
+
});
|
|
591
|
+
return { content: [{ type: "text", text: result }] };
|
|
592
|
+
} catch (error) {
|
|
593
|
+
const msg = error instanceof Error ? error.message : String(error);
|
|
594
|
+
const hint = recoveryHint("generic", "mcp");
|
|
595
|
+
return {
|
|
596
|
+
content: [{ type: "text", text: `Erro ao executar changes: ${msg}
|
|
546
597
|
${hint}` }],
|
|
547
598
|
isError: true
|
|
548
599
|
};
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@justmpm/ai-tool",
|
|
3
|
-
"version": "1.0
|
|
3
|
+
"version": "1.1.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",
|