threadnote 1.1.4 → 1.1.6

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
@@ -12,6 +12,14 @@ It is intentionally scoped to curated docs, memories, skills, and handoffs.
12
12
  **Walkthrough:** https://kashkovsky.github.io/threadnote/
13
13
  **Wiki:** https://github.com/Kashkovsky/threadnote/wiki
14
14
 
15
+ ## Quickstart
16
+
17
+ ```bash
18
+ curl -fsSL https://raw.githubusercontent.com/Kashkovsky/threadnote/main/scripts/install.sh | sh
19
+ threadnote mcp-install claude --apply # or codex / cursor / copilot
20
+ threadnote doctor --dry-run
21
+ ```
22
+
15
23
  ## Real-World Uses
16
24
 
17
25
  **Want to continue work in a fresh agent session?**
@@ -36192,6 +36192,7 @@ function grepUrisFromJson(output) {
36192
36192
  return [];
36193
36193
  }
36194
36194
  }
36195
+ var RECALL_CATEGORY_ORDER = ["memories", "resources", "skills"];
36195
36196
  function recallSnippet(value) {
36196
36197
  if (typeof value !== "string") {
36197
36198
  return "";
@@ -36217,7 +36218,7 @@ function parseRecallHits(output, options = {}) {
36217
36218
  return [];
36218
36219
  }
36219
36220
  const hits = [];
36220
- for (const key of ["memories", "resources", "skills"]) {
36221
+ for (const key of RECALL_CATEGORY_ORDER) {
36221
36222
  const items = result[key];
36222
36223
  if (!Array.isArray(items)) {
36223
36224
  continue;
@@ -36230,6 +36231,7 @@ function parseRecallHits(output, options = {}) {
36230
36231
  continue;
36231
36232
  }
36232
36233
  hits.push({
36234
+ category: key,
36233
36235
  contextType: typeof item.context_type === "string" ? item.context_type : "result",
36234
36236
  score: typeof item.score === "number" ? item.score : 0,
36235
36237
  snippet: recallSnippet(item.abstract ?? item.overview),
@@ -36253,7 +36255,9 @@ function mergeRecallHits(passes) {
36253
36255
  }
36254
36256
  }
36255
36257
  }
36256
- return [...byDocument.values()].sort((left, right) => right.score - left.score);
36258
+ return [...byDocument.values()].sort(
36259
+ (left, right) => RECALL_CATEGORY_ORDER.indexOf(left.category) - RECALL_CATEGORY_ORDER.indexOf(right.category) || right.score - left.score
36260
+ );
36257
36261
  }
36258
36262
  function formatRecallHits(hits, maxHits) {
36259
36263
  if (hits.length === 0) {
@@ -4348,6 +4348,7 @@ function grepUrisFromJson(output2) {
4348
4348
  return [];
4349
4349
  }
4350
4350
  }
4351
+ var RECALL_CATEGORY_ORDER = ["memories", "resources", "skills"];
4351
4352
  function recallSnippet(value) {
4352
4353
  if (typeof value !== "string") {
4353
4354
  return "";
@@ -4373,7 +4374,7 @@ function parseRecallHits(output2, options = {}) {
4373
4374
  return [];
4374
4375
  }
4375
4376
  const hits = [];
4376
- for (const key of ["memories", "resources", "skills"]) {
4377
+ for (const key of RECALL_CATEGORY_ORDER) {
4377
4378
  const items = result[key];
4378
4379
  if (!Array.isArray(items)) {
4379
4380
  continue;
@@ -4386,6 +4387,7 @@ function parseRecallHits(output2, options = {}) {
4386
4387
  continue;
4387
4388
  }
4388
4389
  hits.push({
4390
+ category: key,
4389
4391
  contextType: typeof item.context_type === "string" ? item.context_type : "result",
4390
4392
  score: typeof item.score === "number" ? item.score : 0,
4391
4393
  snippet: recallSnippet(item.abstract ?? item.overview),
@@ -4409,7 +4411,9 @@ function mergeRecallHits(passes) {
4409
4411
  }
4410
4412
  }
4411
4413
  }
4412
- return [...byDocument.values()].sort((left, right) => right.score - left.score);
4414
+ return [...byDocument.values()].sort(
4415
+ (left, right) => RECALL_CATEGORY_ORDER.indexOf(left.category) - RECALL_CATEGORY_ORDER.indexOf(right.category) || right.score - left.score
4416
+ );
4413
4417
  }
4414
4418
  function formatRecallHits(hits, maxHits) {
4415
4419
  if (hits.length === 0) {
@@ -8498,6 +8502,7 @@ var SHARED_SEGMENT = "shared";
8498
8502
  var SHAREABLE_MEMORY_KIND_DIRS = ["durable"];
8499
8503
  var SHAREABLE_ARTIFACT_DIR = "agent-artifacts";
8500
8504
  var SHAREABLE_TOP_LEVEL_DIRS = [...SHAREABLE_MEMORY_KIND_DIRS, SHAREABLE_ARTIFACT_DIR];
8505
+ var SHAREABLE_ROOT_FILES = ["README.md", "AGENTS.md", "CLAUDE.md", "SKILL.md", ".gitignore"];
8501
8506
  var ARTIFACT_INSTALL_METADATA_VERSION = 1;
8502
8507
  var AUTO_SHARE_FETCH_INTERVAL_MS = 5 * 60 * 1e3;
8503
8508
  var DEFAULT_GIT_REMOTE_NAME = "origin";
@@ -8826,13 +8831,29 @@ async function runShareSync(config, options) {
8826
8831
  }
8827
8832
  const message = options.message ?? `share: sync ${(/* @__PURE__ */ new Date()).toISOString()}`;
8828
8833
  await stageShareableChanges(dryRun, git, worktree);
8829
- await maybeRun(dryRun, git, ["-C", worktree, "commit", "-m", message], { allowFailure: true });
8834
+ const commitResult = await maybeRun(dryRun, git, ["-C", worktree, "commit", "-m", message], { allowFailure: true });
8835
+ if (!dryRun && commitResult && commitResult.exitCode !== 0) {
8836
+ if (await hasUncommittedChanges(worktree)) {
8837
+ throw new Error(
8838
+ `Worktree ${worktree} has uncommitted changes that Threadnote did not auto-commit. Commit, remove, or ignore the remaining files, then rerun \`threadnote share sync\`.
8839
+ Git said: ${commitResult.stderr.trim() || commitResult.stdout.trim() || "unknown git commit error"}`
8840
+ );
8841
+ }
8842
+ throw new Error(
8843
+ `Could not auto-commit share worktree changes in ${worktree}: ${commitResult.stderr.trim() || commitResult.stdout.trim() || "unknown git commit error"}`
8844
+ );
8845
+ }
8846
+ if (!dryRun && await hasUncommittedChanges(worktree)) {
8847
+ throw new Error(
8848
+ `Worktree ${worktree} still has uncommitted changes after staging Threadnote shareable files. Commit, remove, or ignore the remaining files, then rerun \`threadnote share sync\`.`
8849
+ );
8850
+ }
8830
8851
  }
8831
8852
  const beforeRev = await gitOutput(worktree, ["rev-parse", "HEAD"], dryRun);
8832
8853
  await maybeRun(dryRun, git, ["-C", worktree, "fetch", DEFAULT_GIT_REMOTE_NAME]);
8833
- const pullResult = dryRun ? void 0 : await runCommand(git, ["-C", worktree, "pull", "--rebase", DEFAULT_GIT_REMOTE_NAME], { allowFailure: true });
8854
+ const pullResult = dryRun ? void 0 : await runCommand(git, ["-C", worktree, "rebase", "@{u}"], { allowFailure: true });
8834
8855
  if (dryRun) {
8835
- console.log(`Would run: ${formatShellCommand(git, ["-C", worktree, "pull", "--rebase", DEFAULT_GIT_REMOTE_NAME])}`);
8856
+ console.log(`Would run: ${formatShellCommand(git, ["-C", worktree, "rebase", "@{u}"])}`);
8836
8857
  } else if (pullResult && pullResult.exitCode !== 0) {
8837
8858
  if (await exists((0, import_node_path6.join)(team.config.gitdir, "rebase-merge")) || await exists((0, import_node_path6.join)(team.config.gitdir, "rebase-apply"))) {
8838
8859
  throw new Error(
@@ -8841,7 +8862,7 @@ Resolve the conflicts in-place, run \`git -C ${worktree} rebase --continue\` (or
8841
8862
  );
8842
8863
  }
8843
8864
  throw new Error(
8844
- `git pull --rebase failed in ${worktree}: ${pullResult.stderr.trim() || pullResult.stdout.trim() || "unknown error"}`
8865
+ `git rebase @{u} failed in ${worktree}: ${pullResult.stderr.trim() || pullResult.stdout.trim() || "unknown error"}`
8845
8866
  );
8846
8867
  }
8847
8868
  const afterRev = await gitOutput(worktree, ["rev-parse", "HEAD"], dryRun);
@@ -8917,8 +8938,31 @@ async function runShareSyncQuiet(config, state, options) {
8917
8938
  return void 0;
8918
8939
  }
8919
8940
  async function stageShareableChanges(dryRun, git, worktree) {
8920
- const pathspecs = [":(top)README.md", ":(top).gitignore", ...SHAREABLE_TOP_LEVEL_DIRS.map((dir) => `:(top)${dir}`)];
8921
- await maybeRun(dryRun, git, ["-C", worktree, "add", "--", ...pathspecs], { allowFailure: true });
8941
+ const pathspecs = await existingShareablePathspecs(git, worktree);
8942
+ if (pathspecs.length === 0) {
8943
+ return;
8944
+ }
8945
+ await maybeRun(dryRun, git, ["-C", worktree, "add", "-A", "--", ...pathspecs], { allowFailure: true });
8946
+ }
8947
+ async function existingShareablePathspecs(git, worktree) {
8948
+ const rootFiles = await Promise.all(
8949
+ SHAREABLE_ROOT_FILES.map(
8950
+ async (file) => await hasWorktreeOrTrackedPath(git, worktree, file) ? `:(top)${file}` : void 0
8951
+ )
8952
+ );
8953
+ const topLevelDirs = await Promise.all(
8954
+ SHAREABLE_TOP_LEVEL_DIRS.map(
8955
+ async (dir) => await hasWorktreeOrTrackedPath(git, worktree, dir) ? `:(top)${dir}` : void 0
8956
+ )
8957
+ );
8958
+ return [...rootFiles, ...topLevelDirs].filter((pathspec) => pathspec !== void 0);
8959
+ }
8960
+ async function hasWorktreeOrTrackedPath(git, worktree, relativePath) {
8961
+ if (await exists((0, import_node_path6.join)(worktree, relativePath))) {
8962
+ return true;
8963
+ }
8964
+ const result = await runCommand(git, ["-C", worktree, "ls-files", "--", relativePath], { allowFailure: true });
8965
+ return result.exitCode === 0 && result.stdout.trim().length > 0;
8922
8966
  }
8923
8967
  async function publishShareGitChange(worktree, relativePath, commitMessage, options = {}) {
8924
8968
  const dryRun = options.dryRun === true;
package/docs/share.md CHANGED
@@ -194,12 +194,15 @@ threadnote share sync --team friends # other team
194
194
  threadnote share sync --no-push # pull only
195
195
  ```
196
196
 
197
- `share sync` will auto-commit any uncommitted edits in the worktree, fetch and
198
- rebase from the remote, reindex pulled markdown files into OpenViking (so
199
- `recall` finds them immediately), and push. Pass `--no-auto-commit` to refuse
200
- syncing when the worktree is dirty. Automatic recall/read sync never commits a
201
- dirty shared worktree; it warns and leaves that case for explicit
202
- `threadnote share sync`.
197
+ `share sync` will auto-commit edits in Threadnote-managed share paths, fetch and
198
+ rebase onto the configured upstream, reindex pulled markdown files into
199
+ OpenViking (so `recall` finds them immediately), and push. Managed share paths
200
+ are root guidance/metadata files (`README.md`, `AGENTS.md`, `CLAUDE.md`,
201
+ `SKILL.md`, `.gitignore`) plus `durable/` and `agent-artifacts/`. If other dirty
202
+ files remain after staging those paths, sync stops before rebase so you can
203
+ commit, remove, or ignore them. Pass `--no-auto-commit` to refuse syncing when
204
+ the worktree is dirty. Automatic recall/read sync never commits a dirty shared
205
+ worktree; it warns and leaves that case for explicit `threadnote share sync`.
203
206
 
204
207
  ### Take a memory back
205
208
 
@@ -280,8 +283,8 @@ updates the git `origin` URL and verifies it with `git fetch`.
280
283
 
281
284
  ## Conflict resolution
282
285
 
283
- `share sync` uses `git pull --rebase` against the remote. When git can't merge
284
- cleanly:
286
+ `share sync` fetches the remote, then rebases onto the branch's configured
287
+ upstream. When git can't merge cleanly:
285
288
 
286
289
  1. The pull command reports the conflict and leaves the worktree in a
287
290
  rebase-in-progress state.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "threadnote",
3
- "version": "1.1.4",
3
+ "version": "1.1.6",
4
4
  "type": "commonjs",
5
5
  "main": "dist/threadnote.cjs",
6
6
  "description": "Shared local context and handoffs for development agents",
@@ -48,7 +48,7 @@
48
48
  "lint": "eslint \"src/**/*.{ts,tsx}\" \"test/**/*.ts\"",
49
49
  "lint:fix": "eslint \"src/**/*.{ts,tsx}\" \"test/**/*.ts\" --fix",
50
50
  "prepare": "husky",
51
- "precommit": "npm run lint && npm run prettier:check && npm run test",
51
+ "precommit": "npm run lint && npm run prettier:write && npm run test",
52
52
  "prepack": "npm run build",
53
53
  "prettier:check": "prettier --check \"**/*.{ts,tsx,js,cjs,json,md,yaml,yml,css,html}\"",
54
54
  "prettier:write": "prettier --write \"**/*.{ts,tsx,js,cjs,json,md,yaml,yml,css,html}\"",