@justmpm/ai-tool 1.0.5 → 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.
|
@@ -196,6 +196,14 @@ var COMMAND_MAP = {
|
|
|
196
196
|
functions: {
|
|
197
197
|
cli: "ai-tool functions",
|
|
198
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' }"
|
|
199
207
|
}
|
|
200
208
|
};
|
|
201
209
|
function hint(command, ctx, params) {
|
|
@@ -260,6 +268,11 @@ var NEXT_STEPS = {
|
|
|
260
268
|
{ command: "find", description: "buscar uma Cloud Function especifica" },
|
|
261
269
|
{ command: "impact", description: "ver impacto de modificar uma function" },
|
|
262
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" }
|
|
263
276
|
]
|
|
264
277
|
};
|
|
265
278
|
function nextSteps(command, ctx) {
|
|
@@ -1534,6 +1547,150 @@ function formatAreaContextText(result, ctx = "cli") {
|
|
|
1534
1547
|
out += nextSteps("area_context", ctx);
|
|
1535
1548
|
return out;
|
|
1536
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
|
+
}
|
|
1537
1694
|
|
|
1538
1695
|
// src/cache/index.ts
|
|
1539
1696
|
import {
|
|
@@ -2669,6 +2826,68 @@ async function getCommitsForFile(filePath, cwd, limit = 10) {
|
|
|
2669
2826
|
return [];
|
|
2670
2827
|
}
|
|
2671
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
|
+
}
|
|
2672
2891
|
|
|
2673
2892
|
// src/commands/impact.ts
|
|
2674
2893
|
async function impact(target, options = {}) {
|
|
@@ -3733,6 +3952,14 @@ function extractTriggerInfo(init, triggerName) {
|
|
|
3733
3952
|
}
|
|
3734
3953
|
|
|
3735
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
|
+
}
|
|
3736
3963
|
function indexProject(cwd) {
|
|
3737
3964
|
const resolvedCwd = cwd || process.cwd();
|
|
3738
3965
|
const allFiles = getAllCodeFiles(resolvedCwd);
|
|
@@ -3810,6 +4037,7 @@ function indexProject(cwd) {
|
|
|
3810
4037
|
const params = func.getParameters().map((p) => p.getName());
|
|
3811
4038
|
const returnType = simplifyType(safeGetReturnType(func));
|
|
3812
4039
|
const kind = inferSymbolKind(name, "function");
|
|
4040
|
+
const description = extractJsDocDescription(func);
|
|
3813
4041
|
const symbol = {
|
|
3814
4042
|
name,
|
|
3815
4043
|
file: filePath,
|
|
@@ -3818,7 +4046,8 @@ function indexProject(cwd) {
|
|
|
3818
4046
|
signature: `${isExported ? "export " : ""}${func.isAsync() ? "async " : ""}function ${name}(${params.join(", ")})`,
|
|
3819
4047
|
isExported,
|
|
3820
4048
|
params,
|
|
3821
|
-
returnType
|
|
4049
|
+
returnType,
|
|
4050
|
+
description
|
|
3822
4051
|
};
|
|
3823
4052
|
symbols.push(symbol);
|
|
3824
4053
|
if (!symbolsByName[name]) {
|
|
@@ -3846,6 +4075,7 @@ function indexProject(cwd) {
|
|
|
3846
4075
|
const params = funcLike.getParameters().map((p) => p.getName());
|
|
3847
4076
|
const returnType = simplifyType(safeGetReturnType(funcLike));
|
|
3848
4077
|
const kind = inferSymbolKind(name, "function");
|
|
4078
|
+
const description = extractVarStatementJsDoc(varStatement);
|
|
3849
4079
|
const symbol = {
|
|
3850
4080
|
name,
|
|
3851
4081
|
file: filePath,
|
|
@@ -3854,7 +4084,8 @@ function indexProject(cwd) {
|
|
|
3854
4084
|
signature: `${isExported ? "export " : ""}const ${name} = (${params.join(", ")}) => ...`,
|
|
3855
4085
|
isExported,
|
|
3856
4086
|
params,
|
|
3857
|
-
returnType
|
|
4087
|
+
returnType,
|
|
4088
|
+
description
|
|
3858
4089
|
};
|
|
3859
4090
|
symbols.push(symbol);
|
|
3860
4091
|
if (!symbolsByName[name]) {
|
|
@@ -3887,6 +4118,7 @@ function indexProject(cwd) {
|
|
|
3887
4118
|
}
|
|
3888
4119
|
if (triggerName && FIREBASE_V2_TRIGGERS.has(triggerName)) {
|
|
3889
4120
|
const triggerInfo = extractTriggerInfo(init, triggerName);
|
|
4121
|
+
const description = extractVarStatementJsDoc(varStatement);
|
|
3890
4122
|
const symbol = {
|
|
3891
4123
|
name,
|
|
3892
4124
|
file: filePath,
|
|
@@ -3894,7 +4126,8 @@ function indexProject(cwd) {
|
|
|
3894
4126
|
kind: "trigger",
|
|
3895
4127
|
signature: `${isExported ? "export " : ""}const ${name} = ${triggerName}(...)`,
|
|
3896
4128
|
isExported,
|
|
3897
|
-
triggerInfo
|
|
4129
|
+
triggerInfo,
|
|
4130
|
+
description
|
|
3898
4131
|
};
|
|
3899
4132
|
symbols.push(symbol);
|
|
3900
4133
|
if (!symbolsByName[name]) {
|
|
@@ -3907,13 +4140,15 @@ function indexProject(cwd) {
|
|
|
3907
4140
|
} else {
|
|
3908
4141
|
const declKind = varStatement.getDeclarationKind();
|
|
3909
4142
|
if (declKind.toString() === "const") {
|
|
4143
|
+
const description = extractVarStatementJsDoc(varStatement);
|
|
3910
4144
|
const symbol = {
|
|
3911
4145
|
name,
|
|
3912
4146
|
file: filePath,
|
|
3913
4147
|
line: varDecl.getStartLineNumber(),
|
|
3914
4148
|
kind: "const",
|
|
3915
4149
|
signature: `${isExported ? "export " : ""}const ${name} = ${truncateCode(init.getText(), 40)}`,
|
|
3916
|
-
isExported
|
|
4150
|
+
isExported,
|
|
4151
|
+
description
|
|
3917
4152
|
};
|
|
3918
4153
|
symbols.push(symbol);
|
|
3919
4154
|
if (!symbolsByName[name]) {
|
|
@@ -3928,13 +4163,15 @@ function indexProject(cwd) {
|
|
|
3928
4163
|
} else {
|
|
3929
4164
|
const declKind = varStatement.getDeclarationKind();
|
|
3930
4165
|
if (declKind.toString() !== "const") continue;
|
|
4166
|
+
const description = extractVarStatementJsDoc(varStatement);
|
|
3931
4167
|
const symbol = {
|
|
3932
4168
|
name,
|
|
3933
4169
|
file: filePath,
|
|
3934
4170
|
line: varDecl.getStartLineNumber(),
|
|
3935
4171
|
kind: "const",
|
|
3936
4172
|
signature: `${isExported ? "export " : ""}const ${name} = ${truncateCode(init.getText(), 40)}`,
|
|
3937
|
-
isExported
|
|
4173
|
+
isExported,
|
|
4174
|
+
description
|
|
3938
4175
|
};
|
|
3939
4176
|
symbols.push(symbol);
|
|
3940
4177
|
if (!symbolsByName[name]) {
|
|
@@ -3950,6 +4187,7 @@ function indexProject(cwd) {
|
|
|
3950
4187
|
for (const iface of sourceFile.getInterfaces()) {
|
|
3951
4188
|
const name = iface.getName();
|
|
3952
4189
|
const isExported = iface.isExported();
|
|
4190
|
+
const description = extractJsDocDescription(iface);
|
|
3953
4191
|
const symbol = {
|
|
3954
4192
|
name,
|
|
3955
4193
|
file: filePath,
|
|
@@ -3957,7 +4195,8 @@ function indexProject(cwd) {
|
|
|
3957
4195
|
kind: "interface",
|
|
3958
4196
|
signature: `${isExported ? "export " : ""}interface ${name}`,
|
|
3959
4197
|
isExported,
|
|
3960
|
-
definition: formatInterfaceDefinition(iface)
|
|
4198
|
+
definition: formatInterfaceDefinition(iface),
|
|
4199
|
+
description
|
|
3961
4200
|
};
|
|
3962
4201
|
symbols.push(symbol);
|
|
3963
4202
|
if (!symbolsByName[name]) {
|
|
@@ -3971,6 +4210,7 @@ function indexProject(cwd) {
|
|
|
3971
4210
|
for (const typeAlias of sourceFile.getTypeAliases()) {
|
|
3972
4211
|
const name = typeAlias.getName();
|
|
3973
4212
|
const isExported = typeAlias.isExported();
|
|
4213
|
+
const description = extractJsDocDescription(typeAlias);
|
|
3974
4214
|
const symbol = {
|
|
3975
4215
|
name,
|
|
3976
4216
|
file: filePath,
|
|
@@ -3978,7 +4218,8 @@ function indexProject(cwd) {
|
|
|
3978
4218
|
kind: "type",
|
|
3979
4219
|
signature: `${isExported ? "export " : ""}type ${name}`,
|
|
3980
4220
|
isExported,
|
|
3981
|
-
definition: simplifyType(safeGetTypeText(() => typeAlias.getType()))
|
|
4221
|
+
definition: simplifyType(safeGetTypeText(() => typeAlias.getType())),
|
|
4222
|
+
description
|
|
3982
4223
|
};
|
|
3983
4224
|
symbols.push(symbol);
|
|
3984
4225
|
if (!symbolsByName[name]) {
|
|
@@ -3992,6 +4233,7 @@ function indexProject(cwd) {
|
|
|
3992
4233
|
for (const enumDecl of sourceFile.getEnums()) {
|
|
3993
4234
|
const name = enumDecl.getName();
|
|
3994
4235
|
const isExported = enumDecl.isExported();
|
|
4236
|
+
const description = extractJsDocDescription(enumDecl);
|
|
3995
4237
|
const symbol = {
|
|
3996
4238
|
name,
|
|
3997
4239
|
file: filePath,
|
|
@@ -3999,7 +4241,8 @@ function indexProject(cwd) {
|
|
|
3999
4241
|
kind: "enum",
|
|
4000
4242
|
signature: `${isExported ? "export " : ""}enum ${name}`,
|
|
4001
4243
|
isExported,
|
|
4002
|
-
definition: enumDecl.getMembers().map((m) => m.getName()).join(" | ")
|
|
4244
|
+
definition: enumDecl.getMembers().map((m) => m.getName()).join(" | "),
|
|
4245
|
+
description
|
|
4003
4246
|
};
|
|
4004
4247
|
symbols.push(symbol);
|
|
4005
4248
|
if (!symbolsByName[name]) {
|
|
@@ -5270,6 +5513,478 @@ function getTriggerIcon(trigger) {
|
|
|
5270
5513
|
return "\u26A1";
|
|
5271
5514
|
}
|
|
5272
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
|
+
|
|
5273
5988
|
// src/commands/find.ts
|
|
5274
5989
|
import { readFileSync as readFileSync4 } from "fs";
|
|
5275
5990
|
import { join as join11 } from "path";
|
|
@@ -5598,5 +6313,6 @@ export {
|
|
|
5598
6313
|
areasInit,
|
|
5599
6314
|
find,
|
|
5600
6315
|
functions,
|
|
6316
|
+
changes,
|
|
5601
6317
|
VERSION
|
|
5602
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",
|