@lisang233/pi-sync 0.2.3 → 0.2.5

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.
package/README.md CHANGED
@@ -19,9 +19,9 @@ detection and merging are handled by git itself.
19
19
  rewritten remote doesn't spuriously conflict.
20
20
  - **Git-native three-way merge** — `/sync pull` merges the remote branch into
21
21
  the local side. Divergent edits keep local configuration protected while
22
- holding conflicts in the mirror; `/sync mergetool` or `--ours`/`--theirs`
23
- resolve them, and `/sync merge` validates (no markers, valid JSON) before
24
- applying to local files.
22
+ holding conflicts in the mirror; `--ours`/`--theirs` or manual edits resolve
23
+ them, and `/sync merge` validates (no markers, valid JSON) before applying to
24
+ local files.
25
25
  - **Deliberate commands** — nothing moves your files without being asked;
26
26
  `automatic` only observes at session start.
27
27
  - **Sensible defaults** — one `pi-sync.json` points at a git remote and branch,
@@ -48,7 +48,6 @@ pi install npm:@lisang233/pi-sync
48
48
  /sync status # config + sync state + next step (--diff for content)
49
49
  /sync fetch # pull the remote tree without applying
50
50
  /sync pull # fetch + merge (--force overwrites local)
51
- /sync mergetool # launch git mergetool for conflicts ([tool])
52
51
  /sync merge # complete merge (--abort, --ours, --theirs)
53
52
  /sync push # publish the local tree (--force overwrites remote)
54
53
  ```
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@lisang233/pi-sync",
3
- "version": "0.2.3",
3
+ "version": "0.2.5",
4
4
  "description": "Personal Pi extension that syncs Pi configuration through Git with background auto-sync and git-style fetch/merge conflict handling.",
5
5
  "type": "module",
6
6
  "license": "MIT",
package/src/extension.ts CHANGED
@@ -15,17 +15,7 @@ interface BackgroundSync {
15
15
  settled: Promise<void>;
16
16
  }
17
17
 
18
- const COMMANDS = [
19
- "init",
20
- "status",
21
- "push",
22
- "pull",
23
- "fetch",
24
- "merge",
25
- "mergetool",
26
- "config",
27
- "help",
28
- ] as const;
18
+ const COMMANDS = ["init", "status", "push", "pull", "fetch", "merge", "config", "help"] as const;
29
19
 
30
20
  type Subcommand = (typeof COMMANDS)[number];
31
21
 
@@ -41,7 +31,6 @@ const USAGE = [
41
31
  " fetch fetch the remote tree without applying",
42
32
  " pull fetch + merge (--force overwrites local)",
43
33
  " merge complete merge (--abort, --ours, --theirs)",
44
- " mergetool launch git mergetool for conflicts ([tool])",
45
34
  " push publish local tree (--force overwrites remote)",
46
35
  " help show this help",
47
36
  ].join("\n");
@@ -226,11 +215,6 @@ async function handleCommand(
226
215
  theirs: restTokens.some((token) => token === "--theirs"),
227
216
  });
228
217
  return;
229
- case "mergetool": {
230
- const tool = restTokens.find((token) => !token.startsWith("-"));
231
- await operations.mergetool(ctx, config, tool);
232
- return;
233
- }
234
218
  case "config":
235
219
  await runConfigEditor(ctx.ui, config);
236
220
  return;
package/src/git.ts CHANGED
@@ -386,30 +386,6 @@ export async function checkoutTheirs(options: GitRunOptions = {}): Promise<void>
386
386
  await stageAll(options);
387
387
  }
388
388
 
389
- /**
390
- * Launch git mergetool interactively in the mirror work tree.
391
- */
392
- export async function launchMergetool(tool?: string, options: GitRunOptions = {}): Promise<void> {
393
- throwIfAborted(options.signal);
394
- const repo = gitCwd();
395
- const args = ["mergetool", "-y"];
396
- if (tool && tool.trim().length > 0) {
397
- args.push(`--tool=${tool.trim()}`);
398
- }
399
- await new Promise<void>((resolve, reject) => {
400
- const child = spawn("git", args, {
401
- cwd: repo,
402
- stdio: process.stdin.isTTY ? "inherit" : ["ignore", "pipe", "pipe"],
403
- windowsHide: false,
404
- });
405
- child.on("error", reject);
406
- child.on("close", (code) => {
407
- if (code === 0) resolve();
408
- else reject(new Error(`git mergetool exited with status ${code ?? "unknown"}.`));
409
- });
410
- });
411
- }
412
-
413
389
  /** Abort an in-progress merge, restoring the work tree to the pre-merge state. */
414
390
  export async function abortMerge(options: GitRunOptions = {}): Promise<void> {
415
391
  await runGit(["merge", "--abort"], { cwd: gitCwd(), signal: options.signal });
package/src/operations.ts CHANGED
@@ -6,7 +6,7 @@ import type {
6
6
  ExtensionUIContext,
7
7
  } from "@earendil-works/pi-coding-agent";
8
8
  import type { SyncConfig } from "./config.js";
9
- import { shortId, stateDir } from "./config.js";
9
+ import { mirrorRepoDir, shortId, stateDir } from "./config.js";
10
10
  import { formatConfig } from "./config-ui.js";
11
11
  import { diffSummary, formatDiff } from "./diff.js";
12
12
  import {
@@ -20,7 +20,6 @@ import {
20
20
  ensureMirror,
21
21
  fetchRemote,
22
22
  isMergeInProgress,
23
- launchMergetool,
24
23
  listConflictedPaths,
25
24
  mergeRemote,
26
25
  pushBranch,
@@ -34,7 +33,6 @@ import {
34
33
  import { agentDir } from "./paths.js";
35
34
  import { classifyState, type StateClassify, syncIndicatorText } from "./status.js";
36
35
  import {
37
- agentTarget,
38
36
  collectAgentFiles,
39
37
  copyMirrorToAgent,
40
38
  graftAgentIntoMirror,
@@ -126,7 +124,7 @@ function nextStepHint(info: StateClassify): string {
126
124
  case "behind":
127
125
  return "next: /sync pull to fetch and apply remote changes";
128
126
  case "conflict":
129
- return "next: /sync merge (--ours, --theirs, or mergetool), or /sync pull --force to overwrite local";
127
+ return "next: /sync merge (--ours or --theirs), or /sync pull --force to overwrite local";
130
128
  case "unknown":
131
129
  return "next: /sync fetch to check the remote";
132
130
  }
@@ -229,9 +227,13 @@ export async function pull(
229
227
  // ignore
230
228
  }
231
229
  const conflictList = conflicts.length > 0 ? ` (${conflicts.join(", ")})` : "";
232
- const message = `Merge conflict in ${conflicts.length} file(s)${conflictList}. Local configuration in ${agentDir()} was preserved. Resolve with /sync merge --ours, /sync merge --theirs, or /sync mergetool. Then run /sync merge. (remote ${shortId(
233
- remoteRevision,
234
- )})`;
230
+ const message = [
231
+ `Merge conflict in ${conflicts.length} file(s)${conflictList}.`,
232
+ `Local configuration in ${agentDir()} was preserved clean and untouched.`,
233
+ `Conflict markers are in mirror: ${mirrorRepoDir()}`,
234
+ "Resolve with /sync merge --ours / --theirs, or edit the mirror file, then /sync merge.",
235
+ `(remote ${shortId(remoteRevision)})`,
236
+ ].join("\n");
235
237
  ctx.ui.notify(message, "warning");
236
238
  return { pushed: false, pulled: false, merged: false, message, conflicts };
237
239
  }
@@ -405,17 +407,19 @@ export async function merge(
405
407
  if (errors.length > 0) {
406
408
  const errorDetails = errors
407
409
  .slice(0, 5)
408
- .map((e) => ` - ${agentTarget(e.path)}${e.line ? `:${e.line}` : ""}: ${e.reason}`)
410
+ .map((e) => ` - ${mirrorTarget(e.path)}${e.line ? `:${e.line}` : ""}: ${e.reason}`)
409
411
  .join("\n");
410
412
  const message = [
411
413
  `Cannot complete merge: ${errors.length} unresolved issue(s) detected.`,
412
414
  errorDetails,
413
415
  "",
414
- `To resolve manually, edit files in: ${agentDir()}`,
416
+ `Conflicted file(s) with markers are in mirror:`,
417
+ ` ${mirrorRepoDir()}`,
418
+ "",
419
+ "To resolve manually, edit the file in mirror above (or edit your local file directly).",
415
420
  "Or choose a resolution strategy:",
416
421
  " /sync merge --ours (keep local configuration)",
417
422
  " /sync merge --theirs (use remote configuration)",
418
- " /sync mergetool (open 3-way merge tool)",
419
423
  " /sync merge --abort (discard merge and restore)",
420
424
  ].join("\n");
421
425
  ctx.ui.notify(message, "error");
@@ -432,38 +436,6 @@ export async function merge(
432
436
  return { pushed: false, pulled: false, merged: true, message };
433
437
  }
434
438
 
435
- export async function mergetool(
436
- ctx: CommandContext,
437
- config: SyncConfig,
438
- tool?: string,
439
- ): Promise<SyncResult> {
440
- if (!(await isMergeInProgress({ signal: ctx.signal }))) {
441
- const message =
442
- "No merge in progress. Run /sync pull to merge remote changes into local files.";
443
- ctx.ui.notify(message, "info");
444
- return { pushed: false, pulled: false, merged: false, message };
445
- }
446
-
447
- try {
448
- await launchMergetool(tool, { signal: ctx.signal });
449
- await refreshIndicator(ctx, config);
450
- const conflicts = await listConflictedPaths({ signal: ctx.signal });
451
- if (conflicts.length === 0) {
452
- ctx.ui.notify("Mergetool finished. Run /sync merge to validate and apply.", "info");
453
- } else {
454
- ctx.ui.notify(
455
- `Mergetool finished, but ${conflicts.length} file(s) still conflicted.`,
456
- "warning",
457
- );
458
- }
459
- return { pushed: false, pulled: false, merged: false, message: "mergetool" };
460
- } catch (error) {
461
- const message = `Failed to run mergetool: ${error instanceof Error ? error.message : String(error)}`;
462
- ctx.ui.notify(message, "error");
463
- return { pushed: false, pulled: false, merged: false, message };
464
- }
465
- }
466
-
467
439
  function describeChanges(summary: ReturnType<typeof diffSummary>): string {
468
440
  const parts: string[] = [];
469
441
  if (summary.added > 0) parts.push(`${summary.added} added`);
package/src/status.ts CHANGED
@@ -135,6 +135,6 @@ export function syncIndicatorText(info: SyncStatusInfo): string {
135
135
  case "behind":
136
136
  return `sync: ${info.behind} behind — pull`;
137
137
  case "conflict":
138
- return `sync: conflict (${info.conflicts}) — /sync merge or mergetool`;
138
+ return `sync: conflict (${info.conflicts}) — /sync merge`;
139
139
  }
140
140
  }