@forwardimpact/libwiki 0.2.18 → 0.2.19

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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@forwardimpact/libwiki",
3
- "version": "0.2.18",
3
+ "version": "0.2.19",
4
4
  "description": "Wiki lifecycle primitives — stable memory for agent teams so coordination persists across sessions.",
5
5
  "keywords": [
6
6
  "wiki",
@@ -50,7 +50,7 @@
50
50
  "@forwardimpact/libeval": "^0.1.49",
51
51
  "@forwardimpact/libpreflight": "^0.1.0",
52
52
  "@forwardimpact/libutil": "^0.1.0",
53
- "@forwardimpact/libxmr": "^1.1.0"
53
+ "@forwardimpact/libxmr": "^2.0.0"
54
54
  },
55
55
  "devDependencies": {
56
56
  "@forwardimpact/libmock": "^0.1.0"
@@ -22,7 +22,9 @@ async function pushWiki(wikiSync, runtime, message) {
22
22
  if (!wikiSync) return;
23
23
  try {
24
24
  await wikiSync.inheritIdentity();
25
- const result = await wikiSync.commitAndPush(message);
25
+ // claim/release contract is a 1-line MEMORY.md change; the pathspec keeps
26
+ // foreign uncommitted files from parallel writers out of the commit.
27
+ const result = await wikiSync.commitAndPush(message, ["MEMORY.md"]);
26
28
  if (result.pushed)
27
29
  runtime.proc.stdout.write("push: committed and pushed\n");
28
30
  } catch (err) {
package/src/wiki-sync.js CHANGED
@@ -102,9 +102,13 @@ export class WikiSync {
102
102
  }
103
103
  }
104
104
 
105
- /** Whether the wiki working tree has no uncommitted changes. */
106
- async isClean() {
107
- const r = await this.#git.status({ cwd: this.#wikiDir });
105
+ /**
106
+ * Whether the wiki working tree has no uncommitted changes, optionally
107
+ * limited to `paths`.
108
+ * @param {string[]} [paths] - Pathspecs to scope the check to.
109
+ */
110
+ async isClean(paths) {
111
+ const r = await this.#git.status({ cwd: this.#wikiDir, paths });
108
112
  return r.stdout.trim() === "";
109
113
  }
110
114
 
@@ -119,14 +123,27 @@ export class WikiSync {
119
123
  }
120
124
 
121
125
  /**
122
- * Stage and commit any working-tree changes, then fetch, rebase on
126
+ * Stage and commit working-tree changes, then fetch, rebase on
123
127
  * origin/master (falling back to a merge with -X ours if the rebase fails),
124
128
  * and push if HEAD is ahead of origin/master. The commit gate and the push
125
129
  * gate are independent so a clean tree with local commits still pushes.
130
+ *
131
+ * Without `paths` the commit sweeps the whole tree (`fit-wiki push`
132
+ * contract). With `paths` the commit is pathspec-scoped so foreign residue
133
+ * from parallel writers in the shared workspace is never swept in; the
134
+ * rebase and merge fallback then run with --autostash because that residue
135
+ * stays uncommitted in the tree.
136
+ *
137
+ * @param {string} message - The commit message.
138
+ * @param {string[]} [paths] - Pathspecs limiting what gets committed.
126
139
  */
127
- async commitAndPush(message) {
128
- if (!(await this.isClean())) {
129
- await this.#git.commitAll(message, { cwd: this.#wikiDir });
140
+ async commitAndPush(message, paths) {
141
+ if (!(await this.isClean(paths))) {
142
+ if (paths?.length) {
143
+ await this.#git.commitPaths(message, paths, { cwd: this.#wikiDir });
144
+ } else {
145
+ await this.#git.commitAll(message, { cwd: this.#wikiDir });
146
+ }
130
147
  }
131
148
  if (!(await this.#hasCommitsAhead())) {
132
149
  return { pushed: false, reason: "clean" };
@@ -134,12 +151,14 @@ export class WikiSync {
134
151
  await this.fetch();
135
152
  const rebase = await this.#git.rebase("origin/master", {
136
153
  cwd: this.#wikiDir,
154
+ autostash: true,
137
155
  });
138
156
  if (rebase.exitCode !== 0) {
139
157
  await this.#git.rebaseAbort({ cwd: this.#wikiDir });
140
158
  await this.#git.mergeOursStrategy({
141
159
  cwd: this.#wikiDir,
142
160
  ref: "origin/master",
161
+ autostash: true,
143
162
  });
144
163
  }
145
164
  // Resolve auth first so a misconfigured `resolveToken` still surfaces; the