@lisang233/pi-sync 0.2.2 → 0.2.4

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.2",
3
+ "version": "0.2.4",
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
@@ -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,
@@ -31,8 +30,10 @@ import {
31
30
  resetHard,
32
31
  stageAll,
33
32
  } from "./git.js";
33
+ import { agentDir } from "./paths.js";
34
34
  import { classifyState, type StateClassify, syncIndicatorText } from "./status.js";
35
35
  import {
36
+ agentTarget,
36
37
  collectAgentFiles,
37
38
  copyMirrorToAgent,
38
39
  graftAgentIntoMirror,
@@ -124,7 +125,7 @@ function nextStepHint(info: StateClassify): string {
124
125
  case "behind":
125
126
  return "next: /sync pull to fetch and apply remote changes";
126
127
  case "conflict":
127
- return "next: /sync merge (--ours, --theirs, or mergetool), or /sync pull --force to overwrite local";
128
+ return "next: /sync merge (--ours or --theirs), or /sync pull --force to overwrite local";
128
129
  case "unknown":
129
130
  return "next: /sync fetch to check the remote";
130
131
  }
@@ -227,7 +228,7 @@ export async function pull(
227
228
  // ignore
228
229
  }
229
230
  const conflictList = conflicts.length > 0 ? ` (${conflicts.join(", ")})` : "";
230
- const message = `Merge conflict in ${conflicts.length} file(s)${conflictList}. Local configuration was preserved. Resolve with /sync merge --ours, /sync merge --theirs, or /sync mergetool. Then run /sync merge. (remote ${shortId(
231
+ const message = `Merge conflict in ${conflicts.length} file(s)${conflictList}. Local configuration in ${agentDir()} was preserved. Edit files or resolve with /sync merge --ours / --theirs. Then run /sync merge. (remote ${shortId(
231
232
  remoteRevision,
232
233
  )})`;
233
234
  ctx.ui.notify(message, "warning");
@@ -403,9 +404,18 @@ export async function merge(
403
404
  if (errors.length > 0) {
404
405
  const errorDetails = errors
405
406
  .slice(0, 5)
406
- .map((e) => ` - ${e.path}${e.line ? `:${e.line}` : ""}: ${e.reason}`)
407
+ .map((e) => ` - ${agentTarget(e.path)}${e.line ? `:${e.line}` : ""}: ${e.reason}`)
407
408
  .join("\n");
408
- const message = `Cannot complete merge: ${errors.length} unresolved issue(s) detected.\n${errorDetails}\nResolve them or run /sync merge --abort.`;
409
+ const message = [
410
+ `Cannot complete merge: ${errors.length} unresolved issue(s) detected.`,
411
+ errorDetails,
412
+ "",
413
+ `To resolve manually, edit files in: ${agentDir()}`,
414
+ "Or choose a resolution strategy:",
415
+ " /sync merge --ours (keep local configuration)",
416
+ " /sync merge --theirs (use remote configuration)",
417
+ " /sync merge --abort (discard merge and restore)",
418
+ ].join("\n");
409
419
  ctx.ui.notify(message, "error");
410
420
  return { pushed: false, pulled: false, merged: false, message };
411
421
  }
@@ -420,38 +430,6 @@ export async function merge(
420
430
  return { pushed: false, pulled: false, merged: true, message };
421
431
  }
422
432
 
423
- export async function mergetool(
424
- ctx: CommandContext,
425
- config: SyncConfig,
426
- tool?: string,
427
- ): Promise<SyncResult> {
428
- if (!(await isMergeInProgress({ signal: ctx.signal }))) {
429
- const message =
430
- "No merge in progress. Run /sync pull to merge remote changes into local files.";
431
- ctx.ui.notify(message, "info");
432
- return { pushed: false, pulled: false, merged: false, message };
433
- }
434
-
435
- try {
436
- await launchMergetool(tool, { signal: ctx.signal });
437
- await refreshIndicator(ctx, config);
438
- const conflicts = await listConflictedPaths({ signal: ctx.signal });
439
- if (conflicts.length === 0) {
440
- ctx.ui.notify("Mergetool finished. Run /sync merge to validate and apply.", "info");
441
- } else {
442
- ctx.ui.notify(
443
- `Mergetool finished, but ${conflicts.length} file(s) still conflicted.`,
444
- "warning",
445
- );
446
- }
447
- return { pushed: false, pulled: false, merged: false, message: "mergetool" };
448
- } catch (error) {
449
- const message = `Failed to run mergetool: ${error instanceof Error ? error.message : String(error)}`;
450
- ctx.ui.notify(message, "error");
451
- return { pushed: false, pulled: false, merged: false, message };
452
- }
453
- }
454
-
455
433
  function describeChanges(summary: ReturnType<typeof diffSummary>): string {
456
434
  const parts: string[] = [];
457
435
  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
  }