@justmpm/ai-tool 1.0.4 → 1.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -181,6 +181,10 @@ var COMMAND_MAP = {
181
181
  cli: "ai-tool areas init",
182
182
  mcp: "analyze__aitool_areas_init"
183
183
  },
184
+ areas_missing: {
185
+ cli: "ai-tool areas --missing",
186
+ mcp: "analyze__aitool_list_areas { missing: true }"
187
+ },
184
188
  find: {
185
189
  cli: "ai-tool find <termo>",
186
190
  mcp: "analyze__aitool_find { query: '<termo>' }"
@@ -192,6 +196,14 @@ var COMMAND_MAP = {
192
196
  functions: {
193
197
  cli: "ai-tool functions",
194
198
  mcp: "analyze__aitool_list_functions"
199
+ },
200
+ changes: {
201
+ cli: "ai-tool changes",
202
+ mcp: "analyze__aitool_changes"
203
+ },
204
+ changes_staged: {
205
+ cli: "ai-tool changes --staged",
206
+ mcp: "analyze__aitool_changes { target: 'staged' }"
195
207
  }
196
208
  };
197
209
  function hint(command, ctx, params) {
@@ -256,6 +268,11 @@ var NEXT_STEPS = {
256
268
  { command: "find", description: "buscar uma Cloud Function especifica" },
257
269
  { command: "impact", description: "ver impacto de modificar uma function" },
258
270
  { command: "context", description: "ver assinaturas de uma function" }
271
+ ],
272
+ changes: [
273
+ { command: "context", description: "ver assinaturas atuais de um arquivo modificado" },
274
+ { command: "impact", description: "ver impacto das mudancas em outros arquivos" },
275
+ { command: "suggest", description: "o que ler antes de editar um arquivo modificado" }
259
276
  ]
260
277
  };
261
278
  function nextSteps(command, ctx) {
@@ -471,7 +488,9 @@ function formatMapSummary(result, areasInfo, ctx = "cli") {
471
488
  if (areasInfo && areasInfo.unmappedCount > 0) {
472
489
  out += `\u26A0\uFE0F ${areasInfo.unmappedCount} arquivo(s) sem area definida
473
490
  `;
474
- out += ` \u2192 ${hint("areas_init", ctx)} para configurar
491
+ out += ` \u2192 ${hint("areas_missing", ctx)} para ver quais arquivos estao sem area
492
+ `;
493
+ out += ` \u2192 ${hint("areas_init", ctx)} para criar/editar areas.config
475
494
 
476
495
  `;
477
496
  }
@@ -1528,6 +1547,150 @@ function formatAreaContextText(result, ctx = "cli") {
1528
1547
  out += nextSteps("area_context", ctx);
1529
1548
  return out;
1530
1549
  }
1550
+ function formatChangesText(result, ctx = "cli") {
1551
+ let out = "";
1552
+ out += `CHANGES SUMMARY
1553
+ `;
1554
+ if (result.files.length === 0) {
1555
+ out += `No changes detected.
1556
+ `;
1557
+ out += nextSteps("changes", ctx);
1558
+ return out;
1559
+ }
1560
+ const targetLabel = result.target === "staged" ? "staged" : result.target === "unstaged" ? "unstaged" : "unstaged + staged";
1561
+ out += `Files: ${result.summary.totalFiles} modified (${targetLabel}) | +${result.summary.totalAdded} -${result.summary.totalRemoved}
1562
+ `;
1563
+ const kindCounts = {};
1564
+ for (const file of result.files) {
1565
+ for (const entry of file.changes) {
1566
+ if (!kindCounts[entry.kind]) kindCounts[entry.kind] = { added: 0, modified: 0, removed: 0 };
1567
+ kindCounts[entry.kind][entry.type]++;
1568
+ }
1569
+ }
1570
+ const kindLabels = {
1571
+ function: "Functions",
1572
+ type: "Types",
1573
+ import: "Imports",
1574
+ const: "Consts",
1575
+ component: "Components",
1576
+ hook: "Hooks",
1577
+ enum: "Enums",
1578
+ class: "Classes",
1579
+ config: "Config",
1580
+ other: "Other"
1581
+ };
1582
+ const kindParts = [];
1583
+ for (const [kind, counts] of Object.entries(kindCounts)) {
1584
+ const parts = [];
1585
+ if (counts.added > 0) parts.push(`+${counts.added}`);
1586
+ if (counts.modified > 0) parts.push(`~${counts.modified}`);
1587
+ if (counts.removed > 0) parts.push(`-${counts.removed}`);
1588
+ if (parts.length > 0) kindParts.push(`${kindLabels[kind]}: ${parts.join(" ")}`);
1589
+ }
1590
+ if (kindParts.length > 0) {
1591
+ out += `${kindParts.join(" | ")}
1592
+ `;
1593
+ }
1594
+ if (result.affectedAreas && result.affectedAreas.length > 0) {
1595
+ out += `
1596
+ AFFECTED AREAS:
1597
+ `;
1598
+ for (const area2 of result.affectedAreas) {
1599
+ const desc = area2.description ? ` - ${area2.description}` : "";
1600
+ out += ` ${area2.name} (${area2.fileCount} file${area2.fileCount === 1 ? "" : "s"})${desc}
1601
+ `;
1602
+ }
1603
+ out += "\n";
1604
+ }
1605
+ const semanticFiles = result.files.filter((f) => f.changes.length > 0);
1606
+ const otherFiles = result.files.filter((f) => f.changes.length === 0);
1607
+ const COMPACT_THRESHOLD = 10;
1608
+ if (result.files.length > COMPACT_THRESHOLD) {
1609
+ out += `
1610
+ COMPACT MODE: Too many files. Use --file=<path> for details.
1611
+
1612
+ `;
1613
+ out += `Modified files:
1614
+ `;
1615
+ for (const file of result.files) {
1616
+ const changeCount = file.changes.length;
1617
+ out += ` ${file.path} (+${file.stats.added} -${file.stats.removed})`;
1618
+ if (changeCount > 0) {
1619
+ out += ` - ${changeCount} changes`;
1620
+ } else {
1621
+ out += ` - (no semantic changes)`;
1622
+ }
1623
+ out += `
1624
+ `;
1625
+ }
1626
+ out += `
1627
+ Use: ${hint("changes", ctx)} --file=<arquivo>
1628
+ `;
1629
+ out += nextSteps("changes", ctx);
1630
+ return out;
1631
+ }
1632
+ for (const file of semanticFiles) {
1633
+ out += `
1634
+ --- ${file.path} (+${file.stats.added} -${file.stats.removed}) ---
1635
+ `;
1636
+ const added = file.changes.filter((c) => c.type === "added");
1637
+ const modified = file.changes.filter((c) => c.type === "modified");
1638
+ const removed = file.changes.filter((c) => c.type === "removed");
1639
+ if (added.length > 0) {
1640
+ out += ` ADDED
1641
+ `;
1642
+ for (const entry of added) {
1643
+ const prefix = formatKindIcon(entry.kind);
1644
+ const detail = entry.detail ? ` - ${entry.detail}` : "";
1645
+ out += ` ${prefix} ${entry.name}${detail}
1646
+ `;
1647
+ }
1648
+ }
1649
+ if (modified.length > 0) {
1650
+ out += ` MODIFIED
1651
+ `;
1652
+ for (const entry of modified) {
1653
+ const detail = entry.detail ? ` - ${entry.detail}` : "";
1654
+ out += ` ~ ${entry.name}${detail}
1655
+ `;
1656
+ }
1657
+ }
1658
+ if (removed.length > 0) {
1659
+ out += ` REMOVED
1660
+ `;
1661
+ for (const entry of removed) {
1662
+ out += ` - ${entry.name}
1663
+ `;
1664
+ }
1665
+ }
1666
+ }
1667
+ if (otherFiles.length > 0) {
1668
+ out += `
1669
+ OTHER FILES (${otherFiles.length}): `;
1670
+ out += otherFiles.map((f) => f.path.split("/").pop()).join(", ");
1671
+ out += "\n";
1672
+ }
1673
+ out += nextSteps("changes", ctx);
1674
+ return out;
1675
+ }
1676
+ function formatKindIcon(kind) {
1677
+ switch (kind) {
1678
+ case "function":
1679
+ case "hook":
1680
+ case "component":
1681
+ return "+";
1682
+ case "type":
1683
+ case "enum":
1684
+ return "+";
1685
+ case "import":
1686
+ return "+";
1687
+ case "const":
1688
+ case "class":
1689
+ return "+";
1690
+ default:
1691
+ return "+";
1692
+ }
1693
+ }
1531
1694
 
1532
1695
  // src/cache/index.ts
1533
1696
  import {
@@ -2663,6 +2826,68 @@ async function getCommitsForFile(filePath, cwd, limit = 10) {
2663
2826
  return [];
2664
2827
  }
2665
2828
  }
2829
+ function getDiffArgs(target) {
2830
+ switch (target) {
2831
+ case "staged":
2832
+ return ["--cached"];
2833
+ case "unstaged":
2834
+ return [];
2835
+ case "all":
2836
+ return ["HEAD"];
2837
+ }
2838
+ }
2839
+ function getChangedFiles(target, cwd) {
2840
+ if (!hasGitRepo(cwd)) return [];
2841
+ try {
2842
+ const diffArgs = getDiffArgs(target);
2843
+ const cmd = `git diff --name-only ${diffArgs.join(" ")}`;
2844
+ const output = execSync2(cmd, {
2845
+ cwd,
2846
+ encoding: "utf-8",
2847
+ stdio: ["pipe", "pipe", "pipe"]
2848
+ });
2849
+ return output.trim().split("\n").filter((line) => line.trim() !== "").map((line) => line.trim());
2850
+ } catch {
2851
+ return [];
2852
+ }
2853
+ }
2854
+ function getDiffForFile(filePath, target, cwd) {
2855
+ if (!hasGitRepo(cwd)) return "";
2856
+ try {
2857
+ const diffArgs = getDiffArgs(target);
2858
+ const cmd = `git diff -U0 ${diffArgs.join(" ")} -- "${filePath}"`;
2859
+ const output = execSync2(cmd, {
2860
+ cwd,
2861
+ encoding: "utf-8",
2862
+ stdio: ["pipe", "pipe", "pipe"]
2863
+ });
2864
+ return output;
2865
+ } catch {
2866
+ return "";
2867
+ }
2868
+ }
2869
+ function getDiffStats(filePath, target, cwd) {
2870
+ if (!hasGitRepo(cwd)) return null;
2871
+ try {
2872
+ const diffArgs = getDiffArgs(target);
2873
+ const cmd = `git diff --numstat ${diffArgs.join(" ")} -- "${filePath}"`;
2874
+ const output = execSync2(cmd, {
2875
+ cwd,
2876
+ encoding: "utf-8",
2877
+ stdio: ["pipe", "pipe", "pipe"]
2878
+ });
2879
+ const line = output.trim().split("\n")[0];
2880
+ if (!line) return null;
2881
+ const parts = line.split(" ");
2882
+ if (parts.length < 2) return null;
2883
+ const added = parts[0] === "-" ? 0 : parseInt(parts[0], 10);
2884
+ const removed = parts[1] === "-" ? 0 : parseInt(parts[1], 10);
2885
+ if (Number.isNaN(added) || Number.isNaN(removed)) return null;
2886
+ return { added, removed };
2887
+ } catch {
2888
+ return null;
2889
+ }
2890
+ }
2666
2891
 
2667
2892
  // src/commands/impact.ts
2668
2893
  async function impact(target, options = {}) {
@@ -3727,6 +3952,14 @@ function extractTriggerInfo(init, triggerName) {
3727
3952
  }
3728
3953
 
3729
3954
  // src/ts/cache.ts
3955
+ function extractJsDocDescription(node) {
3956
+ const descriptions = node.getJsDocs().map((d) => d.getDescription().trim()).filter(Boolean);
3957
+ return descriptions.length > 0 ? descriptions.join(" ") : void 0;
3958
+ }
3959
+ function extractVarStatementJsDoc(varStatement) {
3960
+ const descriptions = varStatement.getJsDocs().map((d) => d.getDescription().trim()).filter(Boolean);
3961
+ return descriptions.length > 0 ? descriptions.join(" ") : void 0;
3962
+ }
3730
3963
  function indexProject(cwd) {
3731
3964
  const resolvedCwd = cwd || process.cwd();
3732
3965
  const allFiles = getAllCodeFiles(resolvedCwd);
@@ -3804,6 +4037,7 @@ function indexProject(cwd) {
3804
4037
  const params = func.getParameters().map((p) => p.getName());
3805
4038
  const returnType = simplifyType(safeGetReturnType(func));
3806
4039
  const kind = inferSymbolKind(name, "function");
4040
+ const description = extractJsDocDescription(func);
3807
4041
  const symbol = {
3808
4042
  name,
3809
4043
  file: filePath,
@@ -3812,7 +4046,8 @@ function indexProject(cwd) {
3812
4046
  signature: `${isExported ? "export " : ""}${func.isAsync() ? "async " : ""}function ${name}(${params.join(", ")})`,
3813
4047
  isExported,
3814
4048
  params,
3815
- returnType
4049
+ returnType,
4050
+ description
3816
4051
  };
3817
4052
  symbols.push(symbol);
3818
4053
  if (!symbolsByName[name]) {
@@ -3840,6 +4075,7 @@ function indexProject(cwd) {
3840
4075
  const params = funcLike.getParameters().map((p) => p.getName());
3841
4076
  const returnType = simplifyType(safeGetReturnType(funcLike));
3842
4077
  const kind = inferSymbolKind(name, "function");
4078
+ const description = extractVarStatementJsDoc(varStatement);
3843
4079
  const symbol = {
3844
4080
  name,
3845
4081
  file: filePath,
@@ -3848,7 +4084,8 @@ function indexProject(cwd) {
3848
4084
  signature: `${isExported ? "export " : ""}const ${name} = (${params.join(", ")}) => ...`,
3849
4085
  isExported,
3850
4086
  params,
3851
- returnType
4087
+ returnType,
4088
+ description
3852
4089
  };
3853
4090
  symbols.push(symbol);
3854
4091
  if (!symbolsByName[name]) {
@@ -3881,6 +4118,7 @@ function indexProject(cwd) {
3881
4118
  }
3882
4119
  if (triggerName && FIREBASE_V2_TRIGGERS.has(triggerName)) {
3883
4120
  const triggerInfo = extractTriggerInfo(init, triggerName);
4121
+ const description = extractVarStatementJsDoc(varStatement);
3884
4122
  const symbol = {
3885
4123
  name,
3886
4124
  file: filePath,
@@ -3888,7 +4126,8 @@ function indexProject(cwd) {
3888
4126
  kind: "trigger",
3889
4127
  signature: `${isExported ? "export " : ""}const ${name} = ${triggerName}(...)`,
3890
4128
  isExported,
3891
- triggerInfo
4129
+ triggerInfo,
4130
+ description
3892
4131
  };
3893
4132
  symbols.push(symbol);
3894
4133
  if (!symbolsByName[name]) {
@@ -3901,13 +4140,15 @@ function indexProject(cwd) {
3901
4140
  } else {
3902
4141
  const declKind = varStatement.getDeclarationKind();
3903
4142
  if (declKind.toString() === "const") {
4143
+ const description = extractVarStatementJsDoc(varStatement);
3904
4144
  const symbol = {
3905
4145
  name,
3906
4146
  file: filePath,
3907
4147
  line: varDecl.getStartLineNumber(),
3908
4148
  kind: "const",
3909
4149
  signature: `${isExported ? "export " : ""}const ${name} = ${truncateCode(init.getText(), 40)}`,
3910
- isExported
4150
+ isExported,
4151
+ description
3911
4152
  };
3912
4153
  symbols.push(symbol);
3913
4154
  if (!symbolsByName[name]) {
@@ -3922,13 +4163,15 @@ function indexProject(cwd) {
3922
4163
  } else {
3923
4164
  const declKind = varStatement.getDeclarationKind();
3924
4165
  if (declKind.toString() !== "const") continue;
4166
+ const description = extractVarStatementJsDoc(varStatement);
3925
4167
  const symbol = {
3926
4168
  name,
3927
4169
  file: filePath,
3928
4170
  line: varDecl.getStartLineNumber(),
3929
4171
  kind: "const",
3930
4172
  signature: `${isExported ? "export " : ""}const ${name} = ${truncateCode(init.getText(), 40)}`,
3931
- isExported
4173
+ isExported,
4174
+ description
3932
4175
  };
3933
4176
  symbols.push(symbol);
3934
4177
  if (!symbolsByName[name]) {
@@ -3944,6 +4187,7 @@ function indexProject(cwd) {
3944
4187
  for (const iface of sourceFile.getInterfaces()) {
3945
4188
  const name = iface.getName();
3946
4189
  const isExported = iface.isExported();
4190
+ const description = extractJsDocDescription(iface);
3947
4191
  const symbol = {
3948
4192
  name,
3949
4193
  file: filePath,
@@ -3951,7 +4195,8 @@ function indexProject(cwd) {
3951
4195
  kind: "interface",
3952
4196
  signature: `${isExported ? "export " : ""}interface ${name}`,
3953
4197
  isExported,
3954
- definition: formatInterfaceDefinition(iface)
4198
+ definition: formatInterfaceDefinition(iface),
4199
+ description
3955
4200
  };
3956
4201
  symbols.push(symbol);
3957
4202
  if (!symbolsByName[name]) {
@@ -3965,6 +4210,7 @@ function indexProject(cwd) {
3965
4210
  for (const typeAlias of sourceFile.getTypeAliases()) {
3966
4211
  const name = typeAlias.getName();
3967
4212
  const isExported = typeAlias.isExported();
4213
+ const description = extractJsDocDescription(typeAlias);
3968
4214
  const symbol = {
3969
4215
  name,
3970
4216
  file: filePath,
@@ -3972,7 +4218,8 @@ function indexProject(cwd) {
3972
4218
  kind: "type",
3973
4219
  signature: `${isExported ? "export " : ""}type ${name}`,
3974
4220
  isExported,
3975
- definition: simplifyType(safeGetTypeText(() => typeAlias.getType()))
4221
+ definition: simplifyType(safeGetTypeText(() => typeAlias.getType())),
4222
+ description
3976
4223
  };
3977
4224
  symbols.push(symbol);
3978
4225
  if (!symbolsByName[name]) {
@@ -3986,6 +4233,7 @@ function indexProject(cwd) {
3986
4233
  for (const enumDecl of sourceFile.getEnums()) {
3987
4234
  const name = enumDecl.getName();
3988
4235
  const isExported = enumDecl.isExported();
4236
+ const description = extractJsDocDescription(enumDecl);
3989
4237
  const symbol = {
3990
4238
  name,
3991
4239
  file: filePath,
@@ -3993,7 +4241,8 @@ function indexProject(cwd) {
3993
4241
  kind: "enum",
3994
4242
  signature: `${isExported ? "export " : ""}enum ${name}`,
3995
4243
  isExported,
3996
- definition: enumDecl.getMembers().map((m) => m.getName()).join(" | ")
4244
+ definition: enumDecl.getMembers().map((m) => m.getName()).join(" | "),
4245
+ description
3997
4246
  };
3998
4247
  symbols.push(symbol);
3999
4248
  if (!symbolsByName[name]) {
@@ -5264,6 +5513,478 @@ function getTriggerIcon(trigger) {
5264
5513
  return "\u26A1";
5265
5514
  }
5266
5515
 
5516
+ // src/commands/changes.ts
5517
+ async function changes(options = {}) {
5518
+ const { cwd, format } = parseCommandOptions(options);
5519
+ const ctx = options.ctx || "cli";
5520
+ const target = options.target || "all";
5521
+ if (!hasGitRepo(cwd)) {
5522
+ throw new Error("Nao e um repositorio Git. Execute este comando em um projeto com Git.");
5523
+ }
5524
+ try {
5525
+ const changedFiles = getChangedFiles(target, cwd);
5526
+ if (changedFiles.length === 0) {
5527
+ const result2 = {
5528
+ version: "1.0.0",
5529
+ timestamp: (/* @__PURE__ */ new Date()).toISOString(),
5530
+ target,
5531
+ summary: {
5532
+ totalFiles: 0,
5533
+ totalAdded: 0,
5534
+ totalRemoved: 0
5535
+ },
5536
+ files: []
5537
+ };
5538
+ return formatOutput(result2, format, (r) => formatChangesText(r, ctx));
5539
+ }
5540
+ const DEFAULT_IGNORE = [".analyze/"];
5541
+ const filesFiltered = changedFiles.filter(
5542
+ (f) => !DEFAULT_IGNORE.some((pattern) => f.includes(pattern))
5543
+ );
5544
+ const filesToProcess = options.file ? filesFiltered.filter(
5545
+ (f) => f.toLowerCase().includes(options.file.toLowerCase())
5546
+ ) : filesFiltered;
5547
+ const fileChanges = [];
5548
+ let totalAdded = 0;
5549
+ let totalRemoved = 0;
5550
+ let symbolsIndex = null;
5551
+ for (const filePath of filesToProcess) {
5552
+ const stats = getDiffStats(filePath, target, cwd);
5553
+ const diffText = getDiffForFile(filePath, target, cwd);
5554
+ const entries = classifyChanges(diffText);
5555
+ if (stats) {
5556
+ totalAdded += stats.added;
5557
+ totalRemoved += stats.removed;
5558
+ }
5559
+ const enrichedEntries = enrichWithJsDoc(entries, filePath, cwd, symbolsIndex);
5560
+ fileChanges.push({
5561
+ path: filePath,
5562
+ category: detectCategory(filePath),
5563
+ stats: stats || { added: 0, removed: 0 },
5564
+ changes: enrichedEntries
5565
+ });
5566
+ }
5567
+ const affectedAreas = buildAffectedAreas(fileChanges, cwd);
5568
+ const totalForSummary = options.file ? fileChanges.length : changedFiles.length;
5569
+ const result = {
5570
+ version: "1.0.0",
5571
+ timestamp: (/* @__PURE__ */ new Date()).toISOString(),
5572
+ target,
5573
+ summary: {
5574
+ totalFiles: totalForSummary,
5575
+ totalAdded,
5576
+ totalRemoved
5577
+ },
5578
+ files: fileChanges,
5579
+ affectedAreas: affectedAreas.length > 0 ? affectedAreas : void 0
5580
+ };
5581
+ return formatOutput(result, format, (r) => formatChangesText(r, ctx));
5582
+ } catch (error) {
5583
+ const message = error instanceof Error ? error.message : String(error);
5584
+ throw new Error(`Erro ao executar changes: ${message}`);
5585
+ }
5586
+ }
5587
+ function enrichWithJsDoc(entries, filePath, cwd, cachedIndex) {
5588
+ const needsEnrichment = (e) => e.kind !== "import" && e.kind !== "config" && !e.detail;
5589
+ if (!entries.some(needsEnrichment)) {
5590
+ return entries;
5591
+ }
5592
+ let index = cachedIndex;
5593
+ if (!index) {
5594
+ try {
5595
+ if (isCacheValid(cwd)) {
5596
+ index = getCachedSymbolsIndex(cwd);
5597
+ }
5598
+ if (!index) {
5599
+ index = indexProject(cwd);
5600
+ }
5601
+ } catch {
5602
+ return entries;
5603
+ }
5604
+ }
5605
+ return entries.map((entry) => {
5606
+ if (!needsEnrichment(entry)) return entry;
5607
+ const cached = index.symbolsByName[entry.name];
5608
+ if (cached) {
5609
+ const match = cached.find((s) => s.file === filePath);
5610
+ if (match?.description) {
5611
+ return { ...entry, detail: match.description };
5612
+ }
5613
+ }
5614
+ return entry;
5615
+ });
5616
+ }
5617
+ function buildAffectedAreas(fileChanges, cwd) {
5618
+ if (!configExists(cwd)) return [];
5619
+ try {
5620
+ const areaConfig = readConfig(cwd);
5621
+ const areaMap = /* @__PURE__ */ new Map();
5622
+ const ungroupedFiles = [];
5623
+ for (const file of fileChanges) {
5624
+ const areas2 = detectFileAreas(file.path, areaConfig);
5625
+ if (areas2.length > 0) {
5626
+ const areaId = areas2[0];
5627
+ if (!areaMap.has(areaId)) {
5628
+ areaMap.set(areaId, {
5629
+ name: getAreaName(areaId, areaConfig),
5630
+ description: getAreaDescription(areaId, areaConfig),
5631
+ files: []
5632
+ });
5633
+ }
5634
+ areaMap.get(areaId).files.push(file.path);
5635
+ } else {
5636
+ ungroupedFiles.push(file.path);
5637
+ }
5638
+ }
5639
+ const affectedAreas = [];
5640
+ for (const [id, info] of areaMap) {
5641
+ affectedAreas.push({
5642
+ id,
5643
+ name: info.name,
5644
+ description: info.description,
5645
+ fileCount: info.files.length,
5646
+ files: info.files
5647
+ });
5648
+ }
5649
+ if (ungroupedFiles.length > 0) {
5650
+ affectedAreas.push({
5651
+ id: "ungrouped",
5652
+ name: "ungrouped",
5653
+ fileCount: ungroupedFiles.length,
5654
+ files: ungroupedFiles
5655
+ });
5656
+ }
5657
+ return affectedAreas;
5658
+ } catch {
5659
+ return [];
5660
+ }
5661
+ }
5662
+ function classifyChanges(diffText) {
5663
+ if (!diffText.trim()) return [];
5664
+ const lines = diffText.split("\n");
5665
+ const entries = [];
5666
+ const relevantLines = [];
5667
+ let lineNumber = 0;
5668
+ for (const rawLine of lines) {
5669
+ lineNumber++;
5670
+ if (rawLine.startsWith("@@") || rawLine.startsWith("diff ") || rawLine.startsWith("index ") || rawLine.startsWith("---") || rawLine.startsWith("+++")) {
5671
+ continue;
5672
+ }
5673
+ if (rawLine.trim() === "") continue;
5674
+ if (rawLine.startsWith("+") || rawLine.startsWith("-")) {
5675
+ const content = rawLine.slice(1).trim();
5676
+ if (content.startsWith("//") || content.startsWith("*") || content.startsWith("/*")) {
5677
+ continue;
5678
+ }
5679
+ }
5680
+ if (rawLine.startsWith("+") || rawLine.startsWith("-")) {
5681
+ const content = rawLine.slice(1).trim();
5682
+ if (/^[})\];,]$/.test(content)) {
5683
+ continue;
5684
+ }
5685
+ }
5686
+ if (rawLine.startsWith("+")) {
5687
+ relevantLines.push({ prefix: "add", content: rawLine.slice(1), line: lineNumber });
5688
+ } else if (rawLine.startsWith("-")) {
5689
+ relevantLines.push({ prefix: "remove", content: rawLine.slice(1), line: lineNumber });
5690
+ }
5691
+ }
5692
+ const processed = /* @__PURE__ */ new Set();
5693
+ const removedSymbols = /* @__PURE__ */ new Map();
5694
+ const addedSymbols = /* @__PURE__ */ new Map();
5695
+ for (let i = 0; i < relevantLines.length; i++) {
5696
+ const line = relevantLines[i];
5697
+ const multiReexport = extractMultipleReexports(line.content);
5698
+ if (multiReexport) {
5699
+ for (const name of multiReexport.names) {
5700
+ if (line.prefix === "remove") {
5701
+ if (!removedSymbols.has(name)) {
5702
+ removedSymbols.set(name, line);
5703
+ }
5704
+ } else {
5705
+ if (!addedSymbols.has(name)) {
5706
+ addedSymbols.set(name, line);
5707
+ }
5708
+ }
5709
+ }
5710
+ continue;
5711
+ }
5712
+ const info = extractSymbolInfo(line.content);
5713
+ if (info && info.name && isRelevantSymbol(info)) {
5714
+ if (line.prefix === "remove") {
5715
+ if (!removedSymbols.has(info.name)) {
5716
+ removedSymbols.set(info.name, line);
5717
+ }
5718
+ } else {
5719
+ if (!addedSymbols.has(info.name)) {
5720
+ addedSymbols.set(info.name, line);
5721
+ }
5722
+ }
5723
+ }
5724
+ }
5725
+ const modifiedNames = /* @__PURE__ */ new Set();
5726
+ for (const name of removedSymbols.keys()) {
5727
+ if (addedSymbols.has(name)) {
5728
+ modifiedNames.add(name);
5729
+ }
5730
+ }
5731
+ for (const [name, line] of removedSymbols) {
5732
+ if (!modifiedNames.has(name)) {
5733
+ const info = extractSymbolInfo(line.content);
5734
+ if (info) {
5735
+ processed.add(line.line);
5736
+ entries.push({
5737
+ kind: info.kind,
5738
+ type: "removed",
5739
+ name
5740
+ });
5741
+ }
5742
+ }
5743
+ }
5744
+ for (const name of modifiedNames) {
5745
+ const removedLine = removedSymbols.get(name);
5746
+ const addedLine = addedSymbols.get(name);
5747
+ const removedInfo = extractSymbolInfo(removedLine.content);
5748
+ const addedInfo = extractSymbolInfo(addedLine.content);
5749
+ if (removedInfo && addedInfo) {
5750
+ processed.add(removedLine.line);
5751
+ processed.add(addedLine.line);
5752
+ const detail = generateModifiedDetail(removedLine.content, addedLine.content, addedInfo.kind);
5753
+ entries.push({
5754
+ kind: addedInfo.kind,
5755
+ type: "modified",
5756
+ name,
5757
+ detail
5758
+ });
5759
+ }
5760
+ }
5761
+ for (const [name, line] of addedSymbols) {
5762
+ if (!modifiedNames.has(name) && !processed.has(line.line)) {
5763
+ const info = extractSymbolInfo(line.content);
5764
+ if (info) {
5765
+ processed.add(line.line);
5766
+ entries.push({
5767
+ kind: info.kind,
5768
+ type: "added",
5769
+ name
5770
+ });
5771
+ }
5772
+ }
5773
+ }
5774
+ return entries;
5775
+ }
5776
+ var COMMON_PROPERTIES = /* @__PURE__ */ new Set([
5777
+ "inputSchema",
5778
+ "outputSchema",
5779
+ "annotations",
5780
+ "content",
5781
+ "isError",
5782
+ "description",
5783
+ "title",
5784
+ "name",
5785
+ "version",
5786
+ "type",
5787
+ "properties",
5788
+ "required",
5789
+ "stats",
5790
+ "summary",
5791
+ "totalFiles",
5792
+ "totalAdded",
5793
+ "totalRemoved",
5794
+ "options",
5795
+ "params",
5796
+ "args",
5797
+ "config",
5798
+ "data",
5799
+ "result",
5800
+ "error",
5801
+ "format",
5802
+ "cwd",
5803
+ "target",
5804
+ "file",
5805
+ "cache",
5806
+ "ctx",
5807
+ "value",
5808
+ "label",
5809
+ "default",
5810
+ "enum",
5811
+ "items",
5812
+ "stdio",
5813
+ "encoding",
5814
+ "actions",
5815
+ "reducer",
5816
+ "payload",
5817
+ "state",
5818
+ "children",
5819
+ "className",
5820
+ "sx",
5821
+ "theme"
5822
+ ]);
5823
+ function extractMultipleReexports(content) {
5824
+ const trimmed = content.trim();
5825
+ const typeMatch = trimmed.match(
5826
+ /export\s+type\s+\{([^}]+)\}\s*from\s+['"][^'"]+['"]/
5827
+ );
5828
+ if (typeMatch) {
5829
+ const names = typeMatch[1].split(",").map((n) => n.trim()).filter(Boolean);
5830
+ return names.length > 1 ? { names, kind: "type" } : null;
5831
+ }
5832
+ const valueMatch = trimmed.match(
5833
+ /export\s+\{([^}]+)\}\s*from\s+['"][^'"]+['"]/
5834
+ );
5835
+ if (valueMatch) {
5836
+ const names = valueMatch[1].split(",").map((n) => n.trim()).filter(Boolean);
5837
+ return names.length > 1 ? { names, kind: "import" } : null;
5838
+ }
5839
+ const localTypeMatch = trimmed.match(/export\s+type\s+\{([^}]+)\}/);
5840
+ if (localTypeMatch) {
5841
+ const names = localTypeMatch[1].split(",").map((n) => n.trim()).filter(Boolean);
5842
+ return names.length > 1 ? { names, kind: "type" } : null;
5843
+ }
5844
+ return null;
5845
+ }
5846
+ function isRelevantSymbol(info) {
5847
+ if (info.kind === "import" || info.kind === "type" || info.kind === "enum" || info.kind === "class" || info.kind === "config") {
5848
+ return true;
5849
+ }
5850
+ if (info.kind === "function" || info.kind === "hook" || info.kind === "component") {
5851
+ return true;
5852
+ }
5853
+ if (info.isExported) {
5854
+ return true;
5855
+ }
5856
+ if (info.kind === "const") {
5857
+ if (/^[A-Z_]+$/.test(info.name) && info.name.length > 2) {
5858
+ return true;
5859
+ }
5860
+ if (/^[A-Z]/.test(info.name)) {
5861
+ return true;
5862
+ }
5863
+ if (info.name.startsWith("use") && info.name.length > 3) {
5864
+ return true;
5865
+ }
5866
+ return false;
5867
+ }
5868
+ return true;
5869
+ }
5870
+ function extractSymbolInfo(content) {
5871
+ const trimmed = content.trim();
5872
+ const importMatch = trimmed.match(/import\s+.*from\s+['"](.+)['"]/);
5873
+ if (importMatch) {
5874
+ return { name: importMatch[1], kind: "import", isExported: false };
5875
+ }
5876
+ const reExportTypeMatch = trimmed.match(/export\s+type\s+\{([^}]+)\}\s*from\s+['"]([^'"]+)['"]/);
5877
+ if (reExportTypeMatch) {
5878
+ const names = reExportTypeMatch[1].split(",").map((n) => n.trim()).filter(Boolean);
5879
+ const firstName = names[0] ?? reExportTypeMatch[2];
5880
+ const fromPath = reExportTypeMatch[2];
5881
+ return { name: `${firstName} (from ${fromPath})`, kind: "type", isExported: true };
5882
+ }
5883
+ const reExportMatch = trimmed.match(/export\s+\{([^}]+)\}\s*from\s+['"]([^'"]+)['"]/);
5884
+ if (reExportMatch) {
5885
+ const names = reExportMatch[1].split(",").map((n) => n.trim()).filter(Boolean);
5886
+ const firstName = names[0] ?? reExportMatch[2];
5887
+ const fromPath = reExportMatch[2];
5888
+ return { name: `${firstName} (from ${fromPath})`, kind: "import", isExported: true };
5889
+ }
5890
+ const funcMatch = trimmed.match(/(?:export\s+)?function\s+(\w+)/);
5891
+ if (funcMatch) {
5892
+ const isExported = /^export\s+/.test(trimmed);
5893
+ const kind = detectFunctionKind(funcMatch[1], trimmed);
5894
+ return { name: funcMatch[1], kind, isExported };
5895
+ }
5896
+ const constMatch = trimmed.match(/(?:export\s+)?const\s+(\w+)\s*[=:(]/);
5897
+ if (constMatch) {
5898
+ const isExported = /^export\s+/.test(trimmed);
5899
+ const kind = detectConstKind(constMatch[1]);
5900
+ return { name: constMatch[1], kind, isExported };
5901
+ }
5902
+ const typeMatch = trimmed.match(/(?:export\s+)?(interface|type|enum)\s+(\w+)/);
5903
+ if (typeMatch) {
5904
+ const isExported = /^export\s+/.test(trimmed);
5905
+ const kindMap = {
5906
+ interface: "type",
5907
+ type: "type",
5908
+ enum: "enum"
5909
+ };
5910
+ return { name: typeMatch[2], kind: kindMap[typeMatch[1]], isExported };
5911
+ }
5912
+ const classMatch = trimmed.match(/(?:export\s+)?class\s+(\w+)/);
5913
+ if (classMatch) {
5914
+ const isExported = /^export\s+/.test(trimmed);
5915
+ return { name: classMatch[1], kind: "class", isExported };
5916
+ }
5917
+ const entryMatchFull = content.match(/^(\s{2,})(\w[\w-]*)\s*:\s*[{[]/);
5918
+ if (entryMatchFull && !COMMON_PROPERTIES.has(entryMatchFull[2])) {
5919
+ return { name: entryMatchFull[2], kind: "config", isExported: false };
5920
+ }
5921
+ return null;
5922
+ }
5923
+ function detectFunctionKind(name, content) {
5924
+ if (name.startsWith("use") && name.length > 3) {
5925
+ return "hook";
5926
+ }
5927
+ if (/^[A-Z]/.test(name) && content.includes("(")) {
5928
+ return "component";
5929
+ }
5930
+ return "function";
5931
+ }
5932
+ function detectConstKind(name) {
5933
+ if (name.startsWith("use") && name.length > 3) {
5934
+ return "hook";
5935
+ }
5936
+ if (/^[A-Z][A-Z_]+$/.test(name) && name.length > 1) {
5937
+ return "const";
5938
+ }
5939
+ if (/^[A-Z]/.test(name)) {
5940
+ return "component";
5941
+ }
5942
+ return "const";
5943
+ }
5944
+ function generateModifiedDetail(oldLine, newLine, kind) {
5945
+ const details = [];
5946
+ const oldReturnMatch = oldLine.match(/:\s*(Promise<)?([A-Z]\w+)(\)?>?)\s*$/);
5947
+ const newReturnMatch = newLine.match(/:\s*(Promise<)?([A-Z]\w+)(\)?>?)\s*$/);
5948
+ if (oldReturnMatch && newReturnMatch && oldReturnMatch[0] !== newReturnMatch[0]) {
5949
+ const oldType = oldReturnMatch[0].trim();
5950
+ const newType = newReturnMatch[0].trim();
5951
+ details.push(`return type: ${oldType} -> ${newType}`);
5952
+ }
5953
+ const oldParams = extractParams2(oldLine);
5954
+ const newParams = extractParams2(newLine);
5955
+ if (oldParams !== newParams && oldParams && newParams) {
5956
+ if (oldParams.split(",").length !== newParams.split(",").length) {
5957
+ details.push("params changed");
5958
+ }
5959
+ }
5960
+ const wasAsync = /^\s*async\s+/.test(oldLine) || /export\s+async\s+/.test(oldLine);
5961
+ const isAsync = /^\s*async\s+/.test(newLine) || /export\s+async\s+/.test(newLine);
5962
+ if (!wasAsync && isAsync) {
5963
+ details.push("now async");
5964
+ } else if (wasAsync && !isAsync) {
5965
+ details.push("no longer async");
5966
+ }
5967
+ if (kind === "type") {
5968
+ const oldBody = extractTypeBody(oldLine);
5969
+ const newBody = extractTypeBody(newLine);
5970
+ if (oldBody !== newBody) {
5971
+ details.push("definition changed");
5972
+ }
5973
+ }
5974
+ if (details.length === 0) {
5975
+ details.push("implementation changed");
5976
+ }
5977
+ return details.join(", ");
5978
+ }
5979
+ function extractParams2(line) {
5980
+ const match = line.match(/\(([^)]*)\)/);
5981
+ return match ? match[1].trim() : null;
5982
+ }
5983
+ function extractTypeBody(line) {
5984
+ const eqIndex = line.indexOf("=");
5985
+ return eqIndex >= 0 ? line.slice(eqIndex + 1).trim() : line.trim();
5986
+ }
5987
+
5267
5988
  // src/commands/find.ts
5268
5989
  import { readFileSync as readFileSync4 } from "fs";
5269
5990
  import { join as join11 } from "path";
@@ -5592,5 +6313,6 @@ export {
5592
6313
  areasInit,
5593
6314
  find,
5594
6315
  functions,
6316
+ changes,
5595
6317
  VERSION
5596
6318
  };
@@ -13,7 +13,7 @@ import {
13
13
  parseCommandOptions,
14
14
  readConfig,
15
15
  updateCacheMeta
16
- } from "./chunk-RODPJDYQ.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-NI3IRPT4.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-RODPJDYQ.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-RP4IQ5FW.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-RODPJDYQ.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-NI3IRPT4.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-RODPJDYQ.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.4",
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",