@leantli/agent-handoff 0.5.2 → 0.6.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.
- package/README.md +14 -3
- package/dist/cli.js +9 -2
- package/dist/core.js +25 -1
- package/package.json +1 -1
- package/resources/agent-handoff.SKILL.md +26 -0
package/README.md
CHANGED
|
@@ -66,7 +66,7 @@ For project-specific decisions or branch-specific context:
|
|
|
66
66
|
|
|
67
67
|
```bash
|
|
68
68
|
agent-handoff learn --scope project --kind decision --note "Use TypeScript for the CLI."
|
|
69
|
-
agent-handoff learn --scope branch --kind context --note "This branch is testing
|
|
69
|
+
agent-handoff learn --scope branch --kind context --note "This branch is testing the sync workflow."
|
|
70
70
|
```
|
|
71
71
|
|
|
72
72
|
## Cross-Device Sync
|
|
@@ -74,18 +74,29 @@ agent-handoff learn --scope branch --kind context --note "This branch is testing
|
|
|
74
74
|
Local cross-session memory works immediately after `agent-handoff enable`. No
|
|
75
75
|
git repository is needed for the vault.
|
|
76
76
|
|
|
77
|
-
Cross-device sync is optional.
|
|
78
|
-
|
|
77
|
+
Cross-device sync is optional. Create a dedicated private repository for the
|
|
78
|
+
vault. Do not use a project code repository as the vault. Then run:
|
|
79
79
|
|
|
80
80
|
```bash
|
|
81
81
|
agent-handoff sync init git@github.com:you/agent-handoff-vault.git
|
|
82
82
|
agent-handoff sync
|
|
83
83
|
```
|
|
84
84
|
|
|
85
|
+
Use a private repository because the vault can contain project background,
|
|
86
|
+
preferences, decisions, and handoff notes.
|
|
87
|
+
|
|
85
88
|
Run `agent-handoff sync init <same-git-url>` once on each device that should
|
|
86
89
|
share the vault. After that, run `agent-handoff sync` before starting on another
|
|
87
90
|
device and after writing useful checkpoints.
|
|
88
91
|
|
|
92
|
+
By default the vault lives at `~/.agent-handoff/vault`. Use `--home` or
|
|
93
|
+
`--vault` when you need a different location. `agent-handoff sync` commits local
|
|
94
|
+
vault changes, pulls/rebases remote vault changes, then pushes the result.
|
|
95
|
+
If `sync init` says the local vault has unsynced memory and the remote already
|
|
96
|
+
has data, back up or manually merge the local vault before joining that remote.
|
|
97
|
+
If git reports a conflict or active operation, resolve the conflict in the vault,
|
|
98
|
+
finish or abort the active git operation, then run `agent-handoff sync` again.
|
|
99
|
+
|
|
89
100
|
## How Projects Are Identified
|
|
90
101
|
|
|
91
102
|
By default, `agent-handoff` identifies the current project from the git `origin`
|
package/dist/cli.js
CHANGED
|
@@ -25,7 +25,7 @@ function buildProgram(stdout, stderr, stdin, cwd) {
|
|
|
25
25
|
program
|
|
26
26
|
.name("agent-handoff")
|
|
27
27
|
.description("Shared vault handoff memory for coding agents.")
|
|
28
|
-
.version(
|
|
28
|
+
.version(`agent-handoff ${packageVersion()}`)
|
|
29
29
|
.exitOverride()
|
|
30
30
|
.configureOutput({
|
|
31
31
|
writeOut: (str) => stdout.write(str),
|
|
@@ -94,7 +94,7 @@ function buildProgram(stdout, stderr, stdin, cwd) {
|
|
|
94
94
|
const sync = program.command("sync").description("Sync the handoff vault.");
|
|
95
95
|
sync
|
|
96
96
|
.command("init <git-url>")
|
|
97
|
-
.description("Enable cross-device sync with a private
|
|
97
|
+
.description("Enable cross-device sync with a dedicated private vault repository.")
|
|
98
98
|
.option("--vault <path>", "Vault directory. Defaults to HOME/vault.")
|
|
99
99
|
.action((gitUrl, options) => {
|
|
100
100
|
const result = enableSync({ home: globalHome(program), vault: options.vault, syncUrl: gitUrl });
|
|
@@ -141,6 +141,13 @@ function readNote(options, stdin) {
|
|
|
141
141
|
}
|
|
142
142
|
return readFileSync(0, "utf8");
|
|
143
143
|
}
|
|
144
|
+
function packageVersion() {
|
|
145
|
+
const pkg = JSON.parse(readFileSync(new URL("../package.json", import.meta.url), "utf8"));
|
|
146
|
+
if (typeof pkg.version !== "string") {
|
|
147
|
+
throw new HandoffError("package.json is missing a string version");
|
|
148
|
+
}
|
|
149
|
+
return pkg.version;
|
|
150
|
+
}
|
|
144
151
|
function printProblems(problems, stdout) {
|
|
145
152
|
for (const problem of problems) {
|
|
146
153
|
stdout.write(`- ${problem}\n`);
|
package/dist/core.js
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { spawnSync } from "node:child_process";
|
|
2
2
|
import { existsSync, mkdirSync, readdirSync, readFileSync, rmSync, writeFileSync, } from "node:fs";
|
|
3
3
|
import { hostname, homedir } from "node:os";
|
|
4
|
-
import { join, resolve } from "node:path";
|
|
4
|
+
import { isAbsolute, join, resolve } from "node:path";
|
|
5
5
|
import { fileURLToPath } from "node:url";
|
|
6
6
|
export const DEFAULT_HOME = join(homedir(), ".agent-handoff");
|
|
7
7
|
export const CONFIG_FILE = "config.json";
|
|
@@ -249,6 +249,9 @@ export function syncVault(opts = {}) {
|
|
|
249
249
|
if (syncProblem)
|
|
250
250
|
throw new HandoffError(syncProblem);
|
|
251
251
|
const outputs = [];
|
|
252
|
+
if (hasActiveGitOperation(setup.vault) || hasUnmergedFiles(setup.vault)) {
|
|
253
|
+
throw syncConflictError(setup.vault);
|
|
254
|
+
}
|
|
252
255
|
gitChecked(setup.vault, ["add", "-A"]);
|
|
253
256
|
const staged = gitRun(setup.vault, ["diff", "--cached", "--quiet"]).status !== 0;
|
|
254
257
|
if (staged) {
|
|
@@ -266,6 +269,9 @@ export function syncVault(opts = {}) {
|
|
|
266
269
|
const pull = gitRun(setup.vault, ["pull", "--rebase", "--autostash", "origin", branch]);
|
|
267
270
|
const ignoredEmptyRemotePull = pull.status !== 0 && isEmptyRemotePull(pull.output);
|
|
268
271
|
if (pull.status !== 0 && !ignoredEmptyRemotePull) {
|
|
272
|
+
if (hasActiveGitOperation(setup.vault) || hasUnmergedFiles(setup.vault)) {
|
|
273
|
+
throw syncConflictError(setup.vault, pull.output);
|
|
274
|
+
}
|
|
269
275
|
throw new HandoffError(pull.output.trim() || "git pull failed");
|
|
270
276
|
}
|
|
271
277
|
if (!ignoredEmptyRemotePull && pull.output.trim())
|
|
@@ -721,6 +727,24 @@ function gitRun(root, args) {
|
|
|
721
727
|
output: `${result.stdout ?? ""}${result.stderr ?? ""}${error}`,
|
|
722
728
|
};
|
|
723
729
|
}
|
|
730
|
+
function hasUnmergedFiles(vault) {
|
|
731
|
+
const result = gitRun(vault, ["diff", "--name-only", "--diff-filter=U"]);
|
|
732
|
+
return result.status === 0 && result.output.trim().length > 0;
|
|
733
|
+
}
|
|
734
|
+
function hasActiveGitOperation(vault) {
|
|
735
|
+
return ["rebase-merge", "rebase-apply", "MERGE_HEAD", "CHERRY_PICK_HEAD", "REVERT_HEAD"].some((name) => gitPathExists(vault, name));
|
|
736
|
+
}
|
|
737
|
+
function gitPathExists(vault, name) {
|
|
738
|
+
const path = gitOutput(vault, ["rev-parse", "--git-path", name]);
|
|
739
|
+
if (!path)
|
|
740
|
+
return false;
|
|
741
|
+
return existsSync(isAbsolute(path) ? path : join(vault, path));
|
|
742
|
+
}
|
|
743
|
+
function syncConflictError(vault, detail) {
|
|
744
|
+
const message = `sync conflict in ${vault}; resolve conflicts in that vault, finish or abort the active git operation, then run agent-handoff sync again`;
|
|
745
|
+
const trimmed = detail?.trim();
|
|
746
|
+
return new HandoffError(trimmed ? `${message}\n\n${trimmed}` : message);
|
|
747
|
+
}
|
|
724
748
|
function isEmptyRemotePull(output) {
|
|
725
749
|
const lowered = output.toLowerCase();
|
|
726
750
|
return lowered.includes("couldn't find remote ref") || lowered.includes("could not find remote ref");
|
package/package.json
CHANGED
|
@@ -22,6 +22,32 @@ When beginning work in a repository:
|
|
|
22
22
|
Do not edit `AGENTS.md`, `CLAUDE.md`, or other instruction files to install
|
|
23
23
|
agent-handoff.
|
|
24
24
|
|
|
25
|
+
## Cross-Device Sync Setup
|
|
26
|
+
|
|
27
|
+
Use a dedicated private vault repository for sync. Do not use a project code
|
|
28
|
+
repository as the vault. The vault can contain project context, preferences,
|
|
29
|
+
decisions, and handoff notes.
|
|
30
|
+
|
|
31
|
+
If the user asks you to set up cross-device sync, make sure the vault repository
|
|
32
|
+
they created is private, then run:
|
|
33
|
+
|
|
34
|
+
```bash
|
|
35
|
+
agent-handoff sync init <private-git-url>
|
|
36
|
+
agent-handoff sync
|
|
37
|
+
```
|
|
38
|
+
|
|
39
|
+
On another device, install and enable the CLI, then run `sync init` with the same
|
|
40
|
+
private git URL, `agent-handoff sync`, and `agent-handoff start`.
|
|
41
|
+
|
|
42
|
+
If `sync init` says the local vault has unsynced memory and the remote already
|
|
43
|
+
has data, keep the local files and report it. The user or agent should back up
|
|
44
|
+
or manually merge the local vault before joining that remote.
|
|
45
|
+
|
|
46
|
+
If sync fails because git reports a conflict or active operation, keep the local
|
|
47
|
+
files and report the conflict. The user or agent should resolve it inside the
|
|
48
|
+
vault, finish or abort the active git operation, then run `agent-handoff sync`
|
|
49
|
+
again.
|
|
50
|
+
|
|
25
51
|
## During Work
|
|
26
52
|
|
|
27
53
|
Use `learn` only for stable facts that should survive future sessions, clones,
|