@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
  };
@@ -13,7 +13,7 @@ import {
13
13
  parseCommandOptions,
14
14
  readConfig,
15
15
  updateCacheMeta
16
- } from "./chunk-VG3ZBPEH.js";
16
+ } from "./chunk-XFXRWLQA.js";
17
17
 
18
18
  // src/commands/describe.ts
19
19
  var STOPWORDS = /* @__PURE__ */ new Set([
package/dist/cli.js CHANGED
@@ -1,13 +1,14 @@
1
1
  #!/usr/bin/env node
2
2
  import {
3
3
  describe
4
- } from "./chunk-PJDTFEHK.js";
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-VG3ZBPEH.js";
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-T4MWFGJ5.js");
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-VG3ZBPEH.js";
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-PJDTFEHK.js";
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-VG3ZBPEH.js";
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.5",
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",