repomind 0.7.0 → 0.8.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.
Files changed (2) hide show
  1. package/dist/index.js +231 -12
  2. package/package.json +4 -4
package/dist/index.js CHANGED
@@ -30993,14 +30993,102 @@ async function stageLines(fullPatch, lineSelections) {
30993
30993
 
30994
30994
  // ../../packages/git/src/worktree.ts
30995
30995
  import { join } from "node:path";
30996
+
30997
+ // ../../packages/git/src/verify.ts
30998
+ function parseDiffStats(diff2) {
30999
+ if (!diff2.trim()) {
31000
+ return { files: [], totalAdditions: 0, totalDeletions: 0 };
31001
+ }
31002
+ const files = [];
31003
+ let currentPath = null;
31004
+ let additions = 0;
31005
+ let deletions = 0;
31006
+ const lines = diff2.split("\n");
31007
+ for (const line of lines) {
31008
+ const fileMatch = line.match(/^diff --git a\/.+ b\/(.+)$/);
31009
+ if (fileMatch) {
31010
+ if (currentPath !== null) {
31011
+ files.push({ path: currentPath, additions, deletions });
31012
+ }
31013
+ currentPath = fileMatch[1];
31014
+ additions = 0;
31015
+ deletions = 0;
31016
+ continue;
31017
+ }
31018
+ if (line.startsWith("index ") || line.startsWith("--- ") || line.startsWith("+++ ") || line.startsWith("@@ ") || line.startsWith("Binary ") || line.startsWith("new file") || line.startsWith("deleted file") || line.startsWith("old mode") || line.startsWith("new mode") || line.startsWith("similarity index") || line.startsWith("rename from") || line.startsWith("rename to")) {
31019
+ continue;
31020
+ }
31021
+ if (line.startsWith("+")) {
31022
+ additions++;
31023
+ } else if (line.startsWith("-")) {
31024
+ deletions++;
31025
+ }
31026
+ }
31027
+ if (currentPath !== null) {
31028
+ files.push({ path: currentPath, additions, deletions });
31029
+ }
31030
+ const totalAdditions = files.reduce((s, f) => s + f.additions, 0);
31031
+ const totalDeletions = files.reduce((s, f) => s + f.deletions, 0);
31032
+ return { files, totalAdditions, totalDeletions };
31033
+ }
31034
+ function verifyCommitIntegrity(originalDiff, committedDiff) {
31035
+ const original = parseDiffStats(originalDiff);
31036
+ const committed = parseDiffStats(committedDiff);
31037
+ if (original.totalAdditions !== committed.totalAdditions || original.totalDeletions !== committed.totalDeletions) {
31038
+ return {
31039
+ match: false,
31040
+ details: `Stats divergem: original +${original.totalAdditions}/-${original.totalDeletions}, committed +${committed.totalAdditions}/-${committed.totalDeletions}`
31041
+ };
31042
+ }
31043
+ const originalPaths = original.files.map((f) => f.path).sort();
31044
+ const committedPaths = committed.files.map((f) => f.path).sort();
31045
+ if (originalPaths.length !== committedPaths.length) {
31046
+ return {
31047
+ match: false,
31048
+ details: `Arquivos diferem: original ${originalPaths.length} files, committed ${committedPaths.length} files`
31049
+ };
31050
+ }
31051
+ for (let i = 0; i < originalPaths.length; i++) {
31052
+ if (originalPaths[i] !== committedPaths[i]) {
31053
+ return {
31054
+ match: false,
31055
+ details: `Arquivo diferente: expected "${originalPaths[i]}", got "${committedPaths[i]}"`
31056
+ };
31057
+ }
31058
+ }
31059
+ const committedByPath = new Map(committed.files.map((f) => [f.path, f]));
31060
+ for (const origFile of original.files) {
31061
+ const commFile = committedByPath.get(origFile.path);
31062
+ if (!commFile) {
31063
+ return {
31064
+ match: false,
31065
+ details: `Arquivo "${origFile.path}" nao encontrado no diff committed`
31066
+ };
31067
+ }
31068
+ if (origFile.additions !== commFile.additions || origFile.deletions !== commFile.deletions) {
31069
+ return {
31070
+ match: false,
31071
+ details: `Stats de "${origFile.path}" divergem: original +${origFile.additions}/-${origFile.deletions}, committed +${commFile.additions}/-${commFile.deletions}`
31072
+ };
31073
+ }
31074
+ }
31075
+ return { match: true };
31076
+ }
31077
+ async function captureCommittedDiff(cwd2, commitCount) {
31078
+ return runGitAt(cwd2, ["diff", `HEAD~${commitCount}..HEAD`]);
31079
+ }
31080
+
31081
+ // ../../packages/git/src/worktree.ts
30996
31082
  async function applyPatchInWorktree(wtPath, patch) {
31083
+ const normalizedPatch = patch.endsWith("\n") ? patch : `${patch}
31084
+ `;
30997
31085
  const proc = Bun.spawn(["git", "apply", "--index", "--3way"], {
30998
31086
  cwd: wtPath,
30999
31087
  stdin: "pipe",
31000
31088
  stdout: "pipe",
31001
31089
  stderr: "pipe"
31002
31090
  });
31003
- proc.stdin.write(patch);
31091
+ proc.stdin.write(normalizedPatch);
31004
31092
  proc.stdin.end();
31005
31093
  const stderr = await new Response(proc.stderr).text();
31006
31094
  const code = await proc.exited;
@@ -31017,23 +31105,61 @@ async function getCurrentBranchRef(repoRoot) {
31017
31105
  );
31018
31106
  }
31019
31107
  }
31020
- async function commitViaWorktree(repoRoot, groups) {
31108
+ async function commitViaWorktree(repoRoot, groups, options) {
31109
+ const debug = process.argv.includes("--verbose") || !!process.env.REPOMIND_DEBUG;
31110
+ const log = debug ? (msg) => console.error(`[worktree] ${msg}`) : () => {
31111
+ };
31021
31112
  const branchRef = await getCurrentBranchRef(repoRoot);
31113
+ log(`branch: ${branchRef}`);
31114
+ const totalPatches = groups.reduce((s, g) => s + g.patches.length, 0);
31115
+ log(`groups: ${groups.length}, total patches: ${totalPatches}`);
31116
+ if (totalPatches === 0) {
31117
+ throw new GitError(
31118
+ "Nenhum patch para aplicar \u2014 verifique se os arquivos t\xEAm diff v\xE1lido."
31119
+ );
31120
+ }
31022
31121
  await runGitAt(repoRoot, ["worktree", "prune"]).catch(() => {
31023
31122
  });
31024
31123
  const wtPath = join(repoRoot, ".git", `split-wt-${Date.now()}`);
31124
+ log(`creating worktree at: ${wtPath}`);
31025
31125
  await runGitAt(repoRoot, ["worktree", "add", "--detach", wtPath, "HEAD"]);
31026
31126
  try {
31027
- for (const group of groups) {
31028
- for (const patch of group.patches) {
31029
- await applyPatchInWorktree(wtPath, patch);
31127
+ for (let i = 0; i < groups.length; i++) {
31128
+ const group = groups[i];
31129
+ log(
31130
+ `group ${i + 1}/${groups.length}: ${group.patches.length} patches, msg="${group.message}"`
31131
+ );
31132
+ for (let j = 0; j < group.patches.length; j++) {
31133
+ log(
31134
+ ` applying patch ${j + 1}/${group.patches.length} (${group.patches[j].length} chars)`
31135
+ );
31136
+ await applyPatchInWorktree(wtPath, group.patches[j]);
31030
31137
  }
31031
31138
  await runGitAt(wtPath, ["commit", "-m", group.message]);
31139
+ log(" committed");
31140
+ }
31141
+ if (options?.originalDiff && !options?.skipVerification) {
31142
+ const committedDiff = await captureCommittedDiff(wtPath, groups.length);
31143
+ const verification = verifyCommitIntegrity(
31144
+ options.originalDiff,
31145
+ committedDiff
31146
+ );
31147
+ log(
31148
+ `verification: match=${verification.match}${verification.details ? ` (${verification.details})` : ""}`
31149
+ );
31150
+ if (!verification.match) {
31151
+ throw new GitError(
31152
+ `Verificacao de integridade falhou: ${verification.details ?? "diff mismatch"}. Nenhum commit foi aplicado.`
31153
+ );
31154
+ }
31032
31155
  }
31033
31156
  const wtHead = (await runGitAt(wtPath, ["rev-parse", "HEAD"])).trim();
31157
+ log(`fast-forward: ${branchRef} -> ${wtHead}`);
31034
31158
  await runGitAt(repoRoot, ["update-ref", branchRef, wtHead]);
31035
31159
  await runGitAt(repoRoot, ["reset", "HEAD"]);
31160
+ log("done \u2014 index reset");
31036
31161
  } catch (err) {
31162
+ log(`ERROR: ${err instanceof Error ? err.message : String(err)}`);
31037
31163
  await runGitAt(repoRoot, ["worktree", "remove", "--force", wtPath]).catch(
31038
31164
  () => {
31039
31165
  }
@@ -42951,12 +43077,39 @@ ${issueRef.trim()}`);
42951
43077
  setPhase("committing");
42952
43078
  const code = await deps.runGitCommit(message);
42953
43079
  if (code !== 0) {
42954
- setErrorMsg("git commit falhou. Verifique os hooks do reposit\xF3rio.");
43080
+ setErrorMsg("git commit falhou. Verifique os hooks do repositorio.");
42955
43081
  setPhase("error-commit");
42956
- } else {
42957
- committedMessageRef.current = message;
42958
- setPhase("done");
43082
+ return;
42959
43083
  }
43084
+ if (deps.captureCommittedDiff && deps.verifyCommitIntegrity && deps.getRepoRoot && deps.runGitReset) {
43085
+ setPhase("verifying");
43086
+ try {
43087
+ const repoRoot = await deps.getRepoRoot();
43088
+ const committedDiff = await deps.captureCommittedDiff(repoRoot, 1);
43089
+ const verification = deps.verifyCommitIntegrity(
43090
+ rawDiff,
43091
+ committedDiff
43092
+ );
43093
+ if (!verification.match) {
43094
+ await deps.runGitReset(["--soft", "HEAD~1"]);
43095
+ setErrorMsg(
43096
+ `Verificacao falhou: ${verification.details ?? "diff mismatch"}. Commit desfeito.`
43097
+ );
43098
+ setPhase("error-commit");
43099
+ return;
43100
+ }
43101
+ } catch (verifyErr) {
43102
+ const verbose2 = process.argv.includes("--verbose") || !!process.env.REPOMIND_DEBUG;
43103
+ if (verbose2) {
43104
+ console.error(
43105
+ "[verify] Verificacao pos-commit falhou:",
43106
+ verifyErr
43107
+ );
43108
+ }
43109
+ }
43110
+ }
43111
+ committedMessageRef.current = message;
43112
+ setPhase("done");
42960
43113
  } else if (denied) {
42961
43114
  setPhase("aborted");
42962
43115
  }
@@ -43115,6 +43268,10 @@ ${bodyParts.join("\n\n")}` : newMsg
43115
43268
  /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(Spinner, {}),
43116
43269
  /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(Text, { color: C.dim, children: "Fazendo commit..." })
43117
43270
  ] }),
43271
+ phase === "verifying" && /* @__PURE__ */ (0, import_jsx_runtime6.jsxs)(Box_default, { gap: 1, children: [
43272
+ /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(Spinner, {}),
43273
+ /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(Text, { color: C.dim, children: "Verificando integridade do commit..." })
43274
+ ] }),
43118
43275
  phase === "done" && /* @__PURE__ */ (0, import_jsx_runtime6.jsxs)(Box_default, { gap: 1, children: [
43119
43276
  /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(Text, { color: C.green, children: "\u2728" }),
43120
43277
  /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(Text, { bold: true, children: "Commit realizado com sucesso" })
@@ -43216,6 +43373,17 @@ async function runGitCommit(message) {
43216
43373
  });
43217
43374
  return proc.exited;
43218
43375
  }
43376
+ async function runGitReset(args2) {
43377
+ const proc = Bun.spawn(["git", "reset", ...args2], {
43378
+ stdout: "pipe",
43379
+ stderr: "pipe"
43380
+ });
43381
+ const code = await proc.exited;
43382
+ if (code !== 0) {
43383
+ const stderr = await new Response(proc.stderr).text();
43384
+ throw new Error(`git reset failed: ${stderr}`);
43385
+ }
43386
+ }
43219
43387
  async function commitCommand() {
43220
43388
  const fileConfig = await readRepoConfig();
43221
43389
  let exitCode = 0;
@@ -43230,6 +43398,10 @@ async function commitCommand() {
43230
43398
  apiPost: (path, body, tkn) => apiClient.post(path, body, tkn),
43231
43399
  postStream: (path, body, tkn, timeoutMs) => apiClient.postStream(path, body, tkn, timeoutMs),
43232
43400
  runGitCommit,
43401
+ captureCommittedDiff,
43402
+ verifyCommitIntegrity,
43403
+ getRepoRoot,
43404
+ runGitReset,
43233
43405
  fileConfig
43234
43406
  },
43235
43407
  onExit: (code, message) => {
@@ -44057,6 +44229,7 @@ function SplitApp({
44057
44229
  const [hunksMap, setHunksMap] = (0, import_react72.useState)(/* @__PURE__ */ new Map());
44058
44230
  const [patchMap, setPatchMap] = (0, import_react72.useState)(/* @__PURE__ */ new Map());
44059
44231
  const [authToken, setAuthToken] = (0, import_react72.useState)("");
44232
+ const [originalRawDiff, setOriginalRawDiff] = (0, import_react72.useState)("");
44060
44233
  const [streamPayload, setStreamPayload] = (0, import_react72.useState)(
44061
44234
  null
44062
44235
  );
@@ -44076,7 +44249,11 @@ function SplitApp({
44076
44249
  if (!TERMINAL_PHASES4.includes(phase)) return;
44077
44250
  const code = phase === "done" || phase === "aborted" ? 0 : 1;
44078
44251
  exit();
44079
- onExit(code, phase === "done" ? committedMessagesRef.current : void 0);
44252
+ onExit(
44253
+ code,
44254
+ phase === "done" ? committedMessagesRef.current : void 0,
44255
+ code !== 0 ? errorMsg : void 0
44256
+ );
44080
44257
  }, [phase]);
44081
44258
  (0, import_react72.useEffect)(() => {
44082
44259
  if (phase !== "analyzing") return;
@@ -44120,6 +44297,7 @@ function SplitApp({
44120
44297
  return;
44121
44298
  }
44122
44299
  files = changes.files;
44300
+ setOriginalRawDiff(changes.rawDiff);
44123
44301
  } else {
44124
44302
  const changes = await deps.getAllChanges();
44125
44303
  if (changes.isEmpty) {
@@ -44214,9 +44392,15 @@ function SplitApp({
44214
44392
  { isActive: phase === "review" }
44215
44393
  );
44216
44394
  async function startCommitting() {
44395
+ const debug = process.argv.includes("--verbose") || process.argv.includes("-v") || !!process.env.REPOMIND_DEBUG;
44396
+ const log = debug ? (msg) => console.error(`[split-commit] ${msg}`) : () => {
44397
+ };
44217
44398
  setPhase("committing");
44218
44399
  setCommitProgress(0);
44219
44400
  const activeGroups2 = groups.filter((g) => !skippedGroups.has(g.groupId));
44401
+ log(
44402
+ `activeGroups: ${activeGroups2.length}, patchMap: ${patchMap.size}, hunksMap: ${hunksMap.size}`
44403
+ );
44220
44404
  if (activeGroups2.length === 0) {
44221
44405
  setPhase("aborted");
44222
44406
  return;
@@ -44227,8 +44411,21 @@ function SplitApp({
44227
44411
  hunksMap,
44228
44412
  patchMap
44229
44413
  );
44414
+ log(
44415
+ `worktreeGroups: ${worktreeGroups.length}, patches: [${worktreeGroups.map((g) => g.patches.length).join(",")}]`
44416
+ );
44417
+ for (let gi = 0; gi < worktreeGroups.length; gi++) {
44418
+ log(
44419
+ ` group[${gi}]: ${worktreeGroups[gi].patches.length} patches, msg="${worktreeGroups[gi].message?.slice(0, 60)}"`
44420
+ );
44421
+ }
44230
44422
  const repoRoot = await deps.getRepoRoot();
44231
- await deps.commitViaWorktree(repoRoot, worktreeGroups);
44423
+ log(`repoRoot: ${repoRoot}`);
44424
+ setPhase("verifying");
44425
+ await deps.commitViaWorktree(repoRoot, worktreeGroups, {
44426
+ originalDiff: originalRawDiff
44427
+ });
44428
+ log("commitViaWorktree done (verified)");
44232
44429
  for (let i = 0; i < activeGroups2.length; i++) {
44233
44430
  setCommitProgress(i + 1);
44234
44431
  if (authToken) {
@@ -44241,6 +44438,8 @@ function SplitApp({
44241
44438
  setPhase("done");
44242
44439
  } catch (e) {
44243
44440
  const detail = e instanceof Error ? e.message : "Erro desconhecido";
44441
+ log(`ERROR: ${detail}`);
44442
+ if (e instanceof Error && e.stack) log(e.stack);
44244
44443
  setErrorMsg(`Erro atomico no split: ${detail}`);
44245
44444
  setPhase("error-commit");
44246
44445
  }
@@ -44387,6 +44586,20 @@ function SplitApp({
44387
44586
  activeGroupCount !== 1 ? "s" : ""
44388
44587
  ] }) })
44389
44588
  ] }),
44589
+ phase === "verifying" && /* @__PURE__ */ (0, import_jsx_runtime15.jsxs)(Box_default, { flexDirection: "column", marginTop: 1, children: [
44590
+ activeGroups.map((g, i) => /* @__PURE__ */ (0, import_jsx_runtime15.jsxs)(Box_default, { flexDirection: "column", children: [
44591
+ /* @__PURE__ */ (0, import_jsx_runtime15.jsxs)(Box_default, { gap: 1, alignItems: "center", children: [
44592
+ /* @__PURE__ */ (0, import_jsx_runtime15.jsx)(Text, { color: C.green, children: "\u2713" }),
44593
+ /* @__PURE__ */ (0, import_jsx_runtime15.jsx)(TypeBadge, { type: g.type }),
44594
+ /* @__PURE__ */ (0, import_jsx_runtime15.jsx)(Text, { color: C.green, children: g.description })
44595
+ ] }),
44596
+ i < activeGroups.length - 1 && /* @__PURE__ */ (0, import_jsx_runtime15.jsx)(Text, { color: C.dim, children: "\u2502" })
44597
+ ] }, g.groupId)),
44598
+ /* @__PURE__ */ (0, import_jsx_runtime15.jsxs)(Box_default, { marginTop: 1, gap: 1, children: [
44599
+ /* @__PURE__ */ (0, import_jsx_runtime15.jsx)(Spinner, {}),
44600
+ /* @__PURE__ */ (0, import_jsx_runtime15.jsx)(Text, { color: C.dim, children: "Verificando integridade dos commits..." })
44601
+ ] })
44602
+ ] }),
44390
44603
  phase === "done" && /* @__PURE__ */ (0, import_jsx_runtime15.jsx)(Box_default, { flexDirection: "column", marginTop: 1, children: activeGroups.map((g, i) => /* @__PURE__ */ (0, import_jsx_runtime15.jsxs)(Box_default, { flexDirection: "column", children: [
44391
44604
  /* @__PURE__ */ (0, import_jsx_runtime15.jsxs)(Box_default, { gap: 1, alignItems: "center", children: [
44392
44605
  /* @__PURE__ */ (0, import_jsx_runtime15.jsx)(Text, { color: C.green, children: "\u2713" }),
@@ -44458,6 +44671,7 @@ async function splitCommand() {
44458
44671
  const useStaged = !process.argv.includes("--all");
44459
44672
  let exitCode = 0;
44460
44673
  let completedCommits = [];
44674
+ let errorMessage = "";
44461
44675
  enterImmersive();
44462
44676
  const { waitUntilExit } = render_default(
44463
44677
  import_react73.default.createElement(SplitApp, {
@@ -44484,9 +44698,10 @@ async function splitCommand() {
44484
44698
  fileConfig,
44485
44699
  useStaged
44486
44700
  },
44487
- onExit: (code, commits) => {
44701
+ onExit: (code, commits, error) => {
44488
44702
  exitCode = code;
44489
44703
  completedCommits = commits ?? [];
44704
+ errorMessage = error ?? "";
44490
44705
  }
44491
44706
  })
44492
44707
  );
@@ -44498,6 +44713,10 @@ async function splitCommand() {
44498
44713
  );
44499
44714
  await waitUntilExit2();
44500
44715
  }
44716
+ if (exitCode !== 0 && errorMessage) {
44717
+ console.error(`
44718
+ \u2717 ${errorMessage}`);
44719
+ }
44501
44720
  process.exit(exitCode);
44502
44721
  }
44503
44722
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "repomind",
3
- "version": "0.7.0",
3
+ "version": "0.8.0",
4
4
  "type": "module",
5
5
  "description": "AI-powered git commit messages and repository insights",
6
6
  "keywords": ["git", "ai", "commit", "cli"],
@@ -26,9 +26,9 @@
26
26
  "react": "^19.2.4"
27
27
  },
28
28
  "devDependencies": {
29
- "@repomind/config": "0.0.1",
30
- "@repomind/git": "0.0.1",
31
- "@repomind/types": "0.0.1",
29
+ "@repomind/config": "workspace:*",
30
+ "@repomind/git": "workspace:*",
31
+ "@repomind/types": "workspace:*",
32
32
  "@types/react": "^19.2.14",
33
33
  "esbuild": "^0.25.0",
34
34
  "typescript": "^5.7.0"