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 +8 -0
- package/dist/mcp_server.cjs +6 -2
- package/dist/threadnote.cjs +52 -8
- package/docs/share.md +11 -8
- package/package.json +2 -2
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?**
|
package/dist/mcp_server.cjs
CHANGED
|
@@ -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
|
|
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(
|
|
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) {
|
package/dist/threadnote.cjs
CHANGED
|
@@ -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
|
|
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(
|
|
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, "
|
|
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, "
|
|
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
|
|
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 =
|
|
8921
|
-
|
|
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
|
|
198
|
-
rebase
|
|
199
|
-
`recall` finds them immediately), and push.
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
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`
|
|
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.
|
|
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:
|
|
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}\"",
|