threadnote 0.6.0 → 0.6.2
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 +5 -1
- package/dist/mcp_server.cjs +246 -95
- package/dist/threadnote.cjs +328 -155
- package/docs/agent-instructions.md +8 -0
- package/docs/index.html +185 -106
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -240,7 +240,11 @@ For deterministic moments where the agent shouldn't have to remember, Threadnote
|
|
|
240
240
|
- **`PreCompact`** auto-stores a handoff snapshot for the current repo right before Claude compacts the conversation,
|
|
241
241
|
so the next turn can recall it even if the agent forgot to write a handoff manually.
|
|
242
242
|
- **`SessionStart`** preloads the latest threadnote handoff/feature memory for the current repo into the new session
|
|
243
|
-
context, so the first turn already knows where you left off.
|
|
243
|
+
context, so the first turn already knows where you left off. The same hook also runs a daily-cached check against
|
|
244
|
+
the npm registry and prints a one-line `[threadnote] vX.Y.Z available; run threadnote update` banner when the
|
|
245
|
+
installed version is behind, so you stop missing releases. Set `THREADNOTE_AUTO_UPDATE=1` to skip the nag and have
|
|
246
|
+
the hook spawn `threadnote update --yes` in the background instead — the new version takes effect on the next
|
|
247
|
+
session.
|
|
244
248
|
|
|
245
249
|
Install them per agent (Codex / Cursor / Copilot are no-ops today — Threadnote prints a clear explanation):
|
|
246
250
|
|
package/dist/mcp_server.cjs
CHANGED
|
@@ -1191,14 +1191,14 @@ var require_util = __commonJS({
|
|
|
1191
1191
|
});
|
|
1192
1192
|
}
|
|
1193
1193
|
exports2.useFunc = useFunc;
|
|
1194
|
-
var
|
|
1195
|
-
(function(
|
|
1196
|
-
|
|
1197
|
-
|
|
1198
|
-
})(
|
|
1194
|
+
var Type2;
|
|
1195
|
+
(function(Type3) {
|
|
1196
|
+
Type3[Type3["Num"] = 0] = "Num";
|
|
1197
|
+
Type3[Type3["Str"] = 1] = "Str";
|
|
1198
|
+
})(Type2 || (exports2.Type = Type2 = {}));
|
|
1199
1199
|
function getErrorPath(dataProp, dataPropType, jsPropertySyntax) {
|
|
1200
1200
|
if (dataProp instanceof codegen_1.Name) {
|
|
1201
|
-
const isNumber = dataPropType ===
|
|
1201
|
+
const isNumber = dataPropType === Type2.Num;
|
|
1202
1202
|
return jsPropertySyntax ? isNumber ? (0, codegen_1._)`"[" + ${dataProp} + "]"` : (0, codegen_1._)`"['" + ${dataProp} + "']"` : isNumber ? (0, codegen_1._)`"/" + ${dataProp}` : (0, codegen_1._)`"/" + ${dataProp}.replace(/~/g, "~0").replace(/\\//g, "~1")`;
|
|
1203
1203
|
}
|
|
1204
1204
|
return jsPropertySyntax ? (0, codegen_1.getProperty)(dataProp).toString() : "/" + escapeJsonPointer(dataProp);
|
|
@@ -1499,37 +1499,37 @@ var require_dataType = __commonJS({
|
|
|
1499
1499
|
DataType2[DataType2["Wrong"] = 1] = "Wrong";
|
|
1500
1500
|
})(DataType || (exports2.DataType = DataType = {}));
|
|
1501
1501
|
function getSchemaTypes(schema2) {
|
|
1502
|
-
const
|
|
1503
|
-
const hasNull =
|
|
1502
|
+
const types2 = getJSONTypes(schema2.type);
|
|
1503
|
+
const hasNull = types2.includes("null");
|
|
1504
1504
|
if (hasNull) {
|
|
1505
1505
|
if (schema2.nullable === false)
|
|
1506
1506
|
throw new Error("type: null contradicts nullable: false");
|
|
1507
1507
|
} else {
|
|
1508
|
-
if (!
|
|
1508
|
+
if (!types2.length && schema2.nullable !== void 0) {
|
|
1509
1509
|
throw new Error('"nullable" cannot be used without "type"');
|
|
1510
1510
|
}
|
|
1511
1511
|
if (schema2.nullable === true)
|
|
1512
|
-
|
|
1512
|
+
types2.push("null");
|
|
1513
1513
|
}
|
|
1514
|
-
return
|
|
1514
|
+
return types2;
|
|
1515
1515
|
}
|
|
1516
1516
|
exports2.getSchemaTypes = getSchemaTypes;
|
|
1517
1517
|
function getJSONTypes(ts) {
|
|
1518
|
-
const
|
|
1519
|
-
if (
|
|
1520
|
-
return
|
|
1521
|
-
throw new Error("type must be JSONType or JSONType[]: " +
|
|
1518
|
+
const types2 = Array.isArray(ts) ? ts : ts ? [ts] : [];
|
|
1519
|
+
if (types2.every(rules_1.isJSONType))
|
|
1520
|
+
return types2;
|
|
1521
|
+
throw new Error("type must be JSONType or JSONType[]: " + types2.join(","));
|
|
1522
1522
|
}
|
|
1523
1523
|
exports2.getJSONTypes = getJSONTypes;
|
|
1524
|
-
function coerceAndCheckDataType(it,
|
|
1524
|
+
function coerceAndCheckDataType(it, types2) {
|
|
1525
1525
|
const { gen, data, opts } = it;
|
|
1526
|
-
const coerceTo = coerceToTypes(
|
|
1527
|
-
const checkTypes =
|
|
1526
|
+
const coerceTo = coerceToTypes(types2, opts.coerceTypes);
|
|
1527
|
+
const checkTypes = types2.length > 0 && !(coerceTo.length === 0 && types2.length === 1 && (0, applicability_1.schemaHasRulesForType)(it, types2[0]));
|
|
1528
1528
|
if (checkTypes) {
|
|
1529
|
-
const wrongType = checkDataTypes(
|
|
1529
|
+
const wrongType = checkDataTypes(types2, data, opts.strictNumbers, DataType.Wrong);
|
|
1530
1530
|
gen.if(wrongType, () => {
|
|
1531
1531
|
if (coerceTo.length)
|
|
1532
|
-
coerceData(it,
|
|
1532
|
+
coerceData(it, types2, coerceTo);
|
|
1533
1533
|
else
|
|
1534
1534
|
reportTypeError(it);
|
|
1535
1535
|
});
|
|
@@ -1538,15 +1538,15 @@ var require_dataType = __commonJS({
|
|
|
1538
1538
|
}
|
|
1539
1539
|
exports2.coerceAndCheckDataType = coerceAndCheckDataType;
|
|
1540
1540
|
var COERCIBLE = /* @__PURE__ */ new Set(["string", "number", "integer", "boolean", "null"]);
|
|
1541
|
-
function coerceToTypes(
|
|
1542
|
-
return coerceTypes ?
|
|
1541
|
+
function coerceToTypes(types2, coerceTypes) {
|
|
1542
|
+
return coerceTypes ? types2.filter((t) => COERCIBLE.has(t) || coerceTypes === "array" && t === "array") : [];
|
|
1543
1543
|
}
|
|
1544
|
-
function coerceData(it,
|
|
1544
|
+
function coerceData(it, types2, coerceTo) {
|
|
1545
1545
|
const { gen, data, opts } = it;
|
|
1546
1546
|
const dataType = gen.let("dataType", (0, codegen_1._)`typeof ${data}`);
|
|
1547
1547
|
const coerced = gen.let("coerced", (0, codegen_1._)`undefined`);
|
|
1548
1548
|
if (opts.coerceTypes === "array") {
|
|
1549
|
-
gen.if((0, codegen_1._)`${dataType} == 'object' && Array.isArray(${data}) && ${data}.length == 1`, () => gen.assign(data, (0, codegen_1._)`${data}[0]`).assign(dataType, (0, codegen_1._)`typeof ${data}`).if(checkDataTypes(
|
|
1549
|
+
gen.if((0, codegen_1._)`${dataType} == 'object' && Array.isArray(${data}) && ${data}.length == 1`, () => gen.assign(data, (0, codegen_1._)`${data}[0]`).assign(dataType, (0, codegen_1._)`typeof ${data}`).if(checkDataTypes(types2, data, opts.strictNumbers), () => gen.assign(coerced, data)));
|
|
1550
1550
|
}
|
|
1551
1551
|
gen.if((0, codegen_1._)`${coerced} !== undefined`);
|
|
1552
1552
|
for (const t of coerceTo) {
|
|
@@ -1622,19 +1622,19 @@ var require_dataType = __commonJS({
|
|
|
1622
1622
|
return checkDataType(dataTypes[0], data, strictNums, correct);
|
|
1623
1623
|
}
|
|
1624
1624
|
let cond;
|
|
1625
|
-
const
|
|
1626
|
-
if (
|
|
1625
|
+
const types2 = (0, util_1.toHash)(dataTypes);
|
|
1626
|
+
if (types2.array && types2.object) {
|
|
1627
1627
|
const notObj = (0, codegen_1._)`typeof ${data} != "object"`;
|
|
1628
|
-
cond =
|
|
1629
|
-
delete
|
|
1630
|
-
delete
|
|
1631
|
-
delete
|
|
1628
|
+
cond = types2.null ? notObj : (0, codegen_1._)`!${data} || ${notObj}`;
|
|
1629
|
+
delete types2.null;
|
|
1630
|
+
delete types2.array;
|
|
1631
|
+
delete types2.object;
|
|
1632
1632
|
} else {
|
|
1633
1633
|
cond = codegen_1.nil;
|
|
1634
1634
|
}
|
|
1635
|
-
if (
|
|
1636
|
-
delete
|
|
1637
|
-
for (const t in
|
|
1635
|
+
if (types2.number)
|
|
1636
|
+
delete types2.integer;
|
|
1637
|
+
for (const t in types2)
|
|
1638
1638
|
cond = (0, codegen_1.and)(cond, checkDataType(t, data, strictNums, correct));
|
|
1639
1639
|
return cond;
|
|
1640
1640
|
}
|
|
@@ -2439,9 +2439,9 @@ var require_validate = __commonJS({
|
|
|
2439
2439
|
function typeAndKeywords(it, errsCount) {
|
|
2440
2440
|
if (it.opts.jtd)
|
|
2441
2441
|
return schemaKeywords(it, [], false, errsCount);
|
|
2442
|
-
const
|
|
2443
|
-
const checkedTypes = (0, dataType_1.coerceAndCheckDataType)(it,
|
|
2444
|
-
schemaKeywords(it,
|
|
2442
|
+
const types2 = (0, dataType_1.getSchemaTypes)(it.schema);
|
|
2443
|
+
const checkedTypes = (0, dataType_1.coerceAndCheckDataType)(it, types2);
|
|
2444
|
+
schemaKeywords(it, types2, !checkedTypes, errsCount);
|
|
2445
2445
|
}
|
|
2446
2446
|
function checkRefsAndKeywords(it) {
|
|
2447
2447
|
const { schema: schema2, errSchemaPath, opts, self } = it;
|
|
@@ -2491,7 +2491,7 @@ var require_validate = __commonJS({
|
|
|
2491
2491
|
if (items instanceof codegen_1.Name)
|
|
2492
2492
|
gen.assign((0, codegen_1._)`${evaluated}.items`, items);
|
|
2493
2493
|
}
|
|
2494
|
-
function schemaKeywords(it,
|
|
2494
|
+
function schemaKeywords(it, types2, typeErrors, errsCount) {
|
|
2495
2495
|
const { gen, schema: schema2, data, allErrors, opts, self } = it;
|
|
2496
2496
|
const { RULES } = self;
|
|
2497
2497
|
if (schema2.$ref && (opts.ignoreKeywordsWithRef || !(0, util_1.schemaHasRulesButRef)(schema2, RULES))) {
|
|
@@ -2499,7 +2499,7 @@ var require_validate = __commonJS({
|
|
|
2499
2499
|
return;
|
|
2500
2500
|
}
|
|
2501
2501
|
if (!opts.jtd)
|
|
2502
|
-
checkStrictTypes(it,
|
|
2502
|
+
checkStrictTypes(it, types2);
|
|
2503
2503
|
gen.block(() => {
|
|
2504
2504
|
for (const group of RULES.rules)
|
|
2505
2505
|
groupKeywords(group);
|
|
@@ -2511,7 +2511,7 @@ var require_validate = __commonJS({
|
|
|
2511
2511
|
if (group.type) {
|
|
2512
2512
|
gen.if((0, dataType_2.checkDataType)(group.type, data, opts.strictNumbers));
|
|
2513
2513
|
iterateKeywords(it, group);
|
|
2514
|
-
if (
|
|
2514
|
+
if (types2.length === 1 && types2[0] === group.type && typeErrors) {
|
|
2515
2515
|
gen.else();
|
|
2516
2516
|
(0, dataType_2.reportTypeError)(it);
|
|
2517
2517
|
}
|
|
@@ -2535,27 +2535,27 @@ var require_validate = __commonJS({
|
|
|
2535
2535
|
}
|
|
2536
2536
|
});
|
|
2537
2537
|
}
|
|
2538
|
-
function checkStrictTypes(it,
|
|
2538
|
+
function checkStrictTypes(it, types2) {
|
|
2539
2539
|
if (it.schemaEnv.meta || !it.opts.strictTypes)
|
|
2540
2540
|
return;
|
|
2541
|
-
checkContextTypes(it,
|
|
2541
|
+
checkContextTypes(it, types2);
|
|
2542
2542
|
if (!it.opts.allowUnionTypes)
|
|
2543
|
-
checkMultipleTypes(it,
|
|
2543
|
+
checkMultipleTypes(it, types2);
|
|
2544
2544
|
checkKeywordTypes(it, it.dataTypes);
|
|
2545
2545
|
}
|
|
2546
|
-
function checkContextTypes(it,
|
|
2547
|
-
if (!
|
|
2546
|
+
function checkContextTypes(it, types2) {
|
|
2547
|
+
if (!types2.length)
|
|
2548
2548
|
return;
|
|
2549
2549
|
if (!it.dataTypes.length) {
|
|
2550
|
-
it.dataTypes =
|
|
2550
|
+
it.dataTypes = types2;
|
|
2551
2551
|
return;
|
|
2552
2552
|
}
|
|
2553
|
-
|
|
2553
|
+
types2.forEach((t) => {
|
|
2554
2554
|
if (!includesType(it.dataTypes, t)) {
|
|
2555
2555
|
strictTypesError(it, `type "${t}" not allowed by context "${it.dataTypes.join(",")}"`);
|
|
2556
2556
|
}
|
|
2557
2557
|
});
|
|
2558
|
-
narrowSchemaTypes(it,
|
|
2558
|
+
narrowSchemaTypes(it, types2);
|
|
2559
2559
|
}
|
|
2560
2560
|
function checkMultipleTypes(it, ts) {
|
|
2561
2561
|
if (ts.length > 1 && !(ts.length === 2 && ts.includes("null"))) {
|
|
@@ -9556,9 +9556,9 @@ var ZodUnion = class extends ZodType {
|
|
|
9556
9556
|
return this._def.options;
|
|
9557
9557
|
}
|
|
9558
9558
|
};
|
|
9559
|
-
ZodUnion.create = (
|
|
9559
|
+
ZodUnion.create = (types2, params) => {
|
|
9560
9560
|
return new ZodUnion({
|
|
9561
|
-
options:
|
|
9561
|
+
options: types2,
|
|
9562
9562
|
typeName: ZodFirstPartyTypeKind.ZodUnion,
|
|
9563
9563
|
...processCreateParams(params)
|
|
9564
9564
|
});
|
|
@@ -21542,10 +21542,10 @@ function _property(property, schema2, params) {
|
|
|
21542
21542
|
});
|
|
21543
21543
|
}
|
|
21544
21544
|
// @__NO_SIDE_EFFECTS__
|
|
21545
|
-
function _mime(
|
|
21545
|
+
function _mime(types2, params) {
|
|
21546
21546
|
return new $ZodCheckMimeType({
|
|
21547
21547
|
check: "mime_type",
|
|
21548
|
-
mime:
|
|
21548
|
+
mime: types2,
|
|
21549
21549
|
...normalizeParams(params)
|
|
21550
21550
|
});
|
|
21551
21551
|
}
|
|
@@ -24603,7 +24603,7 @@ var ZodFile = /* @__PURE__ */ $constructor("ZodFile", (inst, def) => {
|
|
|
24603
24603
|
inst._zod.processJSONSchema = (ctx, json3, params) => fileProcessor(inst, ctx, json3, params);
|
|
24604
24604
|
inst.min = (size, params) => inst.check(_minSize(size, params));
|
|
24605
24605
|
inst.max = (size, params) => inst.check(_maxSize(size, params));
|
|
24606
|
-
inst.mime = (
|
|
24606
|
+
inst.mime = (types2, params) => inst.check(_mime(Array.isArray(types2) ? types2 : [types2], params));
|
|
24607
24607
|
});
|
|
24608
24608
|
function file(params) {
|
|
24609
24609
|
return _file(ZodFile, params);
|
|
@@ -27777,15 +27777,15 @@ function parseUnionDef(def, refs) {
|
|
|
27777
27777
|
return asAnyOf(def, refs);
|
|
27778
27778
|
const options = def.options instanceof Map ? Array.from(def.options.values()) : def.options;
|
|
27779
27779
|
if (options.every((x) => x._def.typeName in primitiveMappings && (!x._def.checks || !x._def.checks.length))) {
|
|
27780
|
-
const
|
|
27780
|
+
const types2 = options.reduce((types3, x) => {
|
|
27781
27781
|
const type2 = primitiveMappings[x._def.typeName];
|
|
27782
|
-
return type2 && !
|
|
27782
|
+
return type2 && !types3.includes(type2) ? [...types3, type2] : types3;
|
|
27783
27783
|
}, []);
|
|
27784
27784
|
return {
|
|
27785
|
-
type:
|
|
27785
|
+
type: types2.length > 1 ? types2 : types2[0]
|
|
27786
27786
|
};
|
|
27787
27787
|
} else if (options.every((x) => x._def.typeName === "ZodLiteral" && !x.description)) {
|
|
27788
|
-
const
|
|
27788
|
+
const types2 = options.reduce((acc, x) => {
|
|
27789
27789
|
const type2 = typeof x._def.value;
|
|
27790
27790
|
switch (type2) {
|
|
27791
27791
|
case "string":
|
|
@@ -27804,8 +27804,8 @@ function parseUnionDef(def, refs) {
|
|
|
27804
27804
|
return acc;
|
|
27805
27805
|
}
|
|
27806
27806
|
}, []);
|
|
27807
|
-
if (
|
|
27808
|
-
const uniqueTypes =
|
|
27807
|
+
if (types2.length === options.length) {
|
|
27808
|
+
const uniqueTypes = types2.filter((x, i, a) => a.indexOf(x) === i);
|
|
27809
27809
|
return {
|
|
27810
27810
|
type: uniqueTypes.length > 1 ? uniqueTypes : uniqueTypes[0],
|
|
27811
27811
|
enum: options.reduce((acc, x) => {
|
|
@@ -30950,7 +30950,7 @@ var StdioServerTransport = class {
|
|
|
30950
30950
|
};
|
|
30951
30951
|
|
|
30952
30952
|
// src/mcp_server.ts
|
|
30953
|
-
var
|
|
30953
|
+
var import_promises4 = require("node:fs/promises");
|
|
30954
30954
|
var import_node_os3 = require("node:os");
|
|
30955
30955
|
var import_node_path3 = require("node:path");
|
|
30956
30956
|
|
|
@@ -30958,10 +30958,8 @@ var import_node_path3 = require("node:path");
|
|
|
30958
30958
|
var DEFAULT_ACCOUNT = "local";
|
|
30959
30959
|
var DEFAULT_AGENT_ID = "threadnote";
|
|
30960
30960
|
|
|
30961
|
-
// src/
|
|
30961
|
+
// src/manifest.ts
|
|
30962
30962
|
var import_promises2 = require("node:fs/promises");
|
|
30963
|
-
var import_node_os2 = require("node:os");
|
|
30964
|
-
var import_node_path2 = require("node:path");
|
|
30965
30963
|
|
|
30966
30964
|
// node_modules/js-yaml/dist/js-yaml.mjs
|
|
30967
30965
|
function isNothing(subject) {
|
|
@@ -33542,12 +33540,50 @@ function renamed(from, to) {
|
|
|
33542
33540
|
throw new Error("Function yaml." + from + " is removed in js-yaml 4. Use yaml." + to + " instead, which is now safe by default.");
|
|
33543
33541
|
};
|
|
33544
33542
|
}
|
|
33543
|
+
var Type = type;
|
|
33544
|
+
var Schema = schema;
|
|
33545
|
+
var FAILSAFE_SCHEMA = failsafe;
|
|
33546
|
+
var JSON_SCHEMA = json2;
|
|
33547
|
+
var CORE_SCHEMA = core;
|
|
33548
|
+
var DEFAULT_SCHEMA = _default3;
|
|
33545
33549
|
var load = loader.load;
|
|
33546
33550
|
var loadAll = loader.loadAll;
|
|
33547
33551
|
var dump = dumper.dump;
|
|
33552
|
+
var YAMLException = exception;
|
|
33553
|
+
var types = {
|
|
33554
|
+
binary,
|
|
33555
|
+
float,
|
|
33556
|
+
map: map2,
|
|
33557
|
+
null: _null4,
|
|
33558
|
+
pairs,
|
|
33559
|
+
set: set2,
|
|
33560
|
+
timestamp,
|
|
33561
|
+
bool,
|
|
33562
|
+
int: int2,
|
|
33563
|
+
merge: merge2,
|
|
33564
|
+
omap,
|
|
33565
|
+
seq,
|
|
33566
|
+
str
|
|
33567
|
+
};
|
|
33548
33568
|
var safeLoad = renamed("safeLoad", "load");
|
|
33549
33569
|
var safeLoadAll = renamed("safeLoadAll", "loadAll");
|
|
33550
33570
|
var safeDump = renamed("safeDump", "dump");
|
|
33571
|
+
var jsYaml = {
|
|
33572
|
+
Type,
|
|
33573
|
+
Schema,
|
|
33574
|
+
FAILSAFE_SCHEMA,
|
|
33575
|
+
JSON_SCHEMA,
|
|
33576
|
+
CORE_SCHEMA,
|
|
33577
|
+
DEFAULT_SCHEMA,
|
|
33578
|
+
load,
|
|
33579
|
+
loadAll,
|
|
33580
|
+
dump,
|
|
33581
|
+
YAMLException,
|
|
33582
|
+
types,
|
|
33583
|
+
safeLoad,
|
|
33584
|
+
safeLoadAll,
|
|
33585
|
+
safeDump
|
|
33586
|
+
};
|
|
33551
33587
|
|
|
33552
33588
|
// src/utils.ts
|
|
33553
33589
|
var import_node_child_process = require("node:child_process");
|
|
@@ -33652,6 +33688,9 @@ function expandPath(path) {
|
|
|
33652
33688
|
function getInvocationCwd() {
|
|
33653
33689
|
return process.env.THREADNOTE_CALLER_CWD ?? process.cwd();
|
|
33654
33690
|
}
|
|
33691
|
+
function trimTrailingSlash(value) {
|
|
33692
|
+
return value.endsWith("/") ? value.slice(0, -1) : value;
|
|
33693
|
+
}
|
|
33655
33694
|
function sha256(content) {
|
|
33656
33695
|
return (0, import_node_crypto.createHash)("sha256").update(content).digest("hex");
|
|
33657
33696
|
}
|
|
@@ -33725,6 +33764,74 @@ function uriSegment(value) {
|
|
|
33725
33764
|
const normalized = value.toLowerCase().replace(/[^a-z0-9._-]+/g, "-").replace(/^-+|-+$/g, "");
|
|
33726
33765
|
return normalized.length > 0 ? normalized : "unknown";
|
|
33727
33766
|
}
|
|
33767
|
+
async function inferProjectFromQuery(manifestPath, query) {
|
|
33768
|
+
try {
|
|
33769
|
+
const manifest = await readSeedManifest(manifestPath);
|
|
33770
|
+
const normalized = query.toLowerCase();
|
|
33771
|
+
return manifest.projects.find((project) => normalized.includes(project.name.toLowerCase()));
|
|
33772
|
+
} catch {
|
|
33773
|
+
return void 0;
|
|
33774
|
+
}
|
|
33775
|
+
}
|
|
33776
|
+
async function readSeedManifest(path) {
|
|
33777
|
+
const raw = await (0, import_promises2.readFile)(path, "utf8");
|
|
33778
|
+
const loaded = jsYaml.load(raw);
|
|
33779
|
+
if (!isJsonObject(loaded)) {
|
|
33780
|
+
throw new Error(`Manifest must be an object: ${path}`);
|
|
33781
|
+
}
|
|
33782
|
+
const version2 = readNumber(loaded, "version");
|
|
33783
|
+
const projectsValue = loaded.projects;
|
|
33784
|
+
if (!Array.isArray(projectsValue)) {
|
|
33785
|
+
throw new Error(`Manifest projects must be an array: ${path}`);
|
|
33786
|
+
}
|
|
33787
|
+
const projects = [];
|
|
33788
|
+
for (const projectValue of projectsValue) {
|
|
33789
|
+
if (!isJsonObject(projectValue)) {
|
|
33790
|
+
throw new Error(`Manifest project must be an object: ${path}`);
|
|
33791
|
+
}
|
|
33792
|
+
const seed = readStringArray(projectValue, "seed");
|
|
33793
|
+
projects.push({
|
|
33794
|
+
name: readString(projectValue, "name"),
|
|
33795
|
+
path: readString(projectValue, "path"),
|
|
33796
|
+
seed,
|
|
33797
|
+
uri: readString(projectValue, "uri")
|
|
33798
|
+
});
|
|
33799
|
+
}
|
|
33800
|
+
let futureMonorepo;
|
|
33801
|
+
if (isJsonObject(loaded.future_monorepo)) {
|
|
33802
|
+
futureMonorepo = {
|
|
33803
|
+
pathCandidates: readStringArray(loaded.future_monorepo, "path_candidates"),
|
|
33804
|
+
uri: readString(loaded.future_monorepo, "uri")
|
|
33805
|
+
};
|
|
33806
|
+
}
|
|
33807
|
+
return { futureMonorepo, projects, version: version2 };
|
|
33808
|
+
}
|
|
33809
|
+
function readString(object3, key) {
|
|
33810
|
+
const value = object3[key];
|
|
33811
|
+
if (typeof value !== "string" || value.length === 0) {
|
|
33812
|
+
throw new Error(`Expected non-empty string for ${key}`);
|
|
33813
|
+
}
|
|
33814
|
+
return value;
|
|
33815
|
+
}
|
|
33816
|
+
function readNumber(object3, key) {
|
|
33817
|
+
const value = object3[key];
|
|
33818
|
+
if (typeof value !== "number") {
|
|
33819
|
+
throw new Error(`Expected number for ${key}`);
|
|
33820
|
+
}
|
|
33821
|
+
return value;
|
|
33822
|
+
}
|
|
33823
|
+
function readStringArray(object3, key) {
|
|
33824
|
+
const value = object3[key];
|
|
33825
|
+
if (!Array.isArray(value) || !value.every((item) => typeof item === "string")) {
|
|
33826
|
+
throw new Error(`Expected string array for ${key}`);
|
|
33827
|
+
}
|
|
33828
|
+
return value;
|
|
33829
|
+
}
|
|
33830
|
+
|
|
33831
|
+
// src/share.ts
|
|
33832
|
+
var import_promises3 = require("node:fs/promises");
|
|
33833
|
+
var import_node_os2 = require("node:os");
|
|
33834
|
+
var import_node_path2 = require("node:path");
|
|
33728
33835
|
|
|
33729
33836
|
// src/runtime.ts
|
|
33730
33837
|
function withIdentity(config2, args) {
|
|
@@ -33949,13 +34056,13 @@ async function writeMemoryFile(config2, ov, uri, content, initialMode, dryRun) {
|
|
|
33949
34056
|
console.log(`Would run: ${formatShellCommand(ov, args)}`);
|
|
33950
34057
|
return;
|
|
33951
34058
|
}
|
|
33952
|
-
const stagingDir = await (0,
|
|
34059
|
+
const stagingDir = await (0, import_promises3.mkdtemp)((0, import_node_path2.join)((0, import_node_os2.tmpdir)(), "threadnote-share-"));
|
|
33953
34060
|
const tempPath = (0, import_node_path2.join)(stagingDir, "body.txt");
|
|
33954
34061
|
try {
|
|
33955
|
-
await (0,
|
|
34062
|
+
await (0, import_promises3.writeFile)(tempPath, content, { encoding: "utf8", mode: 384 });
|
|
33956
34063
|
await writeOvFileWithRetry(config2, ov, uri, tempPath, initialMode);
|
|
33957
34064
|
} finally {
|
|
33958
|
-
await (0,
|
|
34065
|
+
await (0, import_promises3.rm)(stagingDir, { force: true, recursive: true });
|
|
33959
34066
|
}
|
|
33960
34067
|
}
|
|
33961
34068
|
async function writeOvFileWithRetry(config2, ov, uri, fromFile, initialMode) {
|
|
@@ -34051,7 +34158,7 @@ async function bestEffortRemoveWorktreeFile(rollbackUri, worktree, label) {
|
|
|
34051
34158
|
if (!relative2) {
|
|
34052
34159
|
return;
|
|
34053
34160
|
}
|
|
34054
|
-
await (0,
|
|
34161
|
+
await (0, import_promises3.rm)((0, import_node_path2.join)(worktree, relative2), { force: true });
|
|
34055
34162
|
}
|
|
34056
34163
|
async function removeMemoryUri(config2, ov, uri, dryRun) {
|
|
34057
34164
|
const args = withIdentity(config2, ["rm", uri]);
|
|
@@ -34091,6 +34198,7 @@ async function main() {
|
|
|
34091
34198
|
"Stdio MCP adapter for Threadnote shared local context.",
|
|
34092
34199
|
"Prefer `recall_context` to find candidate viking:// URIs, then `read_context` files or `list_context` directories.",
|
|
34093
34200
|
'Always pass JSON arguments. Example: recall_context({"query":"current repo latest handoff"}).',
|
|
34201
|
+
"recall_context also surfaces seeded project resources under viking://resources/repos/<project> when the query mentions a project from the seed manifest. See its tool description for the query convention.",
|
|
34094
34202
|
"Older clients may use the compatibility aliases `search`, `read`, and `list`.",
|
|
34095
34203
|
'For durable facts, store kind="durable"; for current work logs, store kind="handoff" with project/topic so Threadnote keeps one active memory updated.',
|
|
34096
34204
|
"When a handoff describes an active branch or feature, recall durable feature memories for the same branch/topic before coding.",
|
|
@@ -34110,6 +34218,7 @@ function getRuntimeConfig() {
|
|
|
34110
34218
|
account: process.env.THREADNOTE_ACCOUNT ?? DEFAULT_ACCOUNT,
|
|
34111
34219
|
agentContextHome: expandPath(process.env.THREADNOTE_HOME ?? "~/.openviking"),
|
|
34112
34220
|
agentId: process.env.THREADNOTE_AGENT_ID ?? DEFAULT_AGENT_ID,
|
|
34221
|
+
manifestPath: expandPath(process.env.THREADNOTE_MANIFEST ?? "~/.openviking/seed-manifest.yaml"),
|
|
34113
34222
|
user: process.env.THREADNOTE_USER ?? process.env.USER ?? "unknown"
|
|
34114
34223
|
};
|
|
34115
34224
|
}
|
|
@@ -34118,13 +34227,13 @@ function registerTools(server, config2) {
|
|
|
34118
34227
|
server,
|
|
34119
34228
|
config2,
|
|
34120
34229
|
"recall_context",
|
|
34121
|
-
|
|
34230
|
+
`Search Threadnote context across personal memories and seeded project resources. Returns semantic hits from indexed Threadnote context (handoffs, durable feature memories, preferences, shared team memories) \u2014 and, when the query mentions a project name from the seed manifest, also from that project's seeded guidance (README, AGENTS.md, CLAUDE.md, SKILL.md, docs/**) under viking://resources/repos/<project>. Include the repo or project name in the query to make the project-guidance pass fire. Required: pass JSON arguments with a non-empty query, for example {"query":"unity-ui-ccc latest handoff"}.`
|
|
34122
34231
|
);
|
|
34123
34232
|
registerSearchTool(
|
|
34124
34233
|
server,
|
|
34125
34234
|
config2,
|
|
34126
34235
|
"search",
|
|
34127
|
-
"Compatibility alias for recall_context.
|
|
34236
|
+
"Compatibility alias for recall_context. Searches both personal memories and seeded project resources; see recall_context for the query conventions."
|
|
34128
34237
|
);
|
|
34129
34238
|
registerReadTool(
|
|
34130
34239
|
server,
|
|
@@ -34324,40 +34433,78 @@ function registerSearchTool(server, config2, name, description) {
|
|
|
34324
34433
|
if (!checkedUri.ok) {
|
|
34325
34434
|
return checkedUri.error;
|
|
34326
34435
|
}
|
|
34327
|
-
return runRecallTool(
|
|
34328
|
-
|
|
34329
|
-
|
|
34330
|
-
|
|
34331
|
-
|
|
34332
|
-
|
|
34333
|
-
...nodeLimit ? ["--node-limit", String(nodeLimit)] : []
|
|
34334
|
-
],
|
|
34335
|
-
includeArchived === true
|
|
34336
|
-
);
|
|
34436
|
+
return runRecallTool(config2, {
|
|
34437
|
+
query: checkedQuery.value,
|
|
34438
|
+
pinnedUri: checkedUri.value,
|
|
34439
|
+
nodeLimit,
|
|
34440
|
+
includeArchived: includeArchived === true
|
|
34441
|
+
});
|
|
34337
34442
|
}
|
|
34338
34443
|
);
|
|
34339
34444
|
}
|
|
34340
|
-
async function runRecallTool(config2,
|
|
34341
|
-
const
|
|
34445
|
+
async function runRecallTool(config2, params) {
|
|
34446
|
+
const baseArgs = [
|
|
34447
|
+
"search",
|
|
34448
|
+
params.query,
|
|
34449
|
+
...params.pinnedUri ? ["--uri", params.pinnedUri] : [],
|
|
34450
|
+
...params.nodeLimit ? ["--node-limit", String(params.nodeLimit)] : []
|
|
34451
|
+
];
|
|
34452
|
+
const semanticResult = await runOpenVikingTool(config2, baseArgs);
|
|
34342
34453
|
if (semanticResult.isError === true) {
|
|
34343
34454
|
return semanticResult;
|
|
34344
34455
|
}
|
|
34345
|
-
const query = args[1];
|
|
34346
|
-
const exactMatches = typeof query === "string" ? await exactMemoryMatchesText(config2, query, includeArchived) : void 0;
|
|
34347
|
-
if (!exactMatches) {
|
|
34348
|
-
return semanticResult;
|
|
34349
|
-
}
|
|
34350
34456
|
const [firstContent] = semanticResult.content;
|
|
34351
34457
|
if (firstContent?.type !== "text") {
|
|
34352
34458
|
return semanticResult;
|
|
34353
34459
|
}
|
|
34354
|
-
|
|
34355
|
-
|
|
34356
|
-
|
|
34357
|
-
|
|
34358
|
-
|
|
34359
|
-
|
|
34360
|
-
|
|
34460
|
+
const sections = [];
|
|
34461
|
+
if (firstContent.text) {
|
|
34462
|
+
sections.push(firstContent.text);
|
|
34463
|
+
}
|
|
34464
|
+
const seededSection = await seededResourcesSection(config2, params);
|
|
34465
|
+
if (seededSection) {
|
|
34466
|
+
sections.push(seededSection);
|
|
34467
|
+
}
|
|
34468
|
+
const exactMatches = await exactMemoryMatchesText(config2, params.query, params.includeArchived);
|
|
34469
|
+
if (exactMatches) {
|
|
34470
|
+
sections.push(`Exact durable memory matches:
|
|
34471
|
+
${exactMatches}`);
|
|
34472
|
+
}
|
|
34473
|
+
if (sections.length <= 1) {
|
|
34474
|
+
return semanticResult;
|
|
34475
|
+
}
|
|
34476
|
+
return { ...semanticResult, content: [{ type: "text", text: sections.join("\n\n") }] };
|
|
34477
|
+
}
|
|
34478
|
+
async function seededResourcesSection(config2, params) {
|
|
34479
|
+
if (params.pinnedUri) {
|
|
34480
|
+
return void 0;
|
|
34481
|
+
}
|
|
34482
|
+
const project = await inferProjectFromQuery(config2.manifestPath, params.query);
|
|
34483
|
+
if (!project) {
|
|
34484
|
+
return void 0;
|
|
34485
|
+
}
|
|
34486
|
+
const projectResourceUri = trimTrailingSlash(project.uri);
|
|
34487
|
+
if (!projectResourceUri.startsWith("viking://")) {
|
|
34488
|
+
return void 0;
|
|
34489
|
+
}
|
|
34490
|
+
const ov = await requiredOpenVikingCli();
|
|
34491
|
+
const args = [
|
|
34492
|
+
"search",
|
|
34493
|
+
params.query,
|
|
34494
|
+
"--uri",
|
|
34495
|
+
projectResourceUri,
|
|
34496
|
+
...params.nodeLimit ? ["--node-limit", String(params.nodeLimit)] : []
|
|
34497
|
+
];
|
|
34498
|
+
const result = await runCommand(ov, withIdentity2(config2, args), { allowFailure: true });
|
|
34499
|
+
if (result.exitCode !== 0) {
|
|
34500
|
+
return void 0;
|
|
34501
|
+
}
|
|
34502
|
+
const body = [result.stdout.trim(), result.stderr.trim()].filter(Boolean).join("\n");
|
|
34503
|
+
if (!body) {
|
|
34504
|
+
return void 0;
|
|
34505
|
+
}
|
|
34506
|
+
return `Seeded project resources (${projectResourceUri}):
|
|
34507
|
+
${body}`;
|
|
34361
34508
|
}
|
|
34362
34509
|
async function exactMemoryMatchesText(config2, query, includeArchived) {
|
|
34363
34510
|
const terms = exactRecallTerms(query);
|
|
@@ -34682,7 +34829,11 @@ function exactMemoryScopes(config2, includeArchived) {
|
|
|
34682
34829
|
`${userBase}/incidents/active`,
|
|
34683
34830
|
`${userBase}/events`,
|
|
34684
34831
|
`${userBase}/shared`,
|
|
34685
|
-
`viking://agent/${uriSegment2(config2.agentId)}/memories
|
|
34832
|
+
`viking://agent/${uriSegment2(config2.agentId)}/memories`,
|
|
34833
|
+
// Seeded project resources live outside the user/memories tree. Include
|
|
34834
|
+
// them so exact-term grep surfaces matches in seeded READMEs, AGENTS.md,
|
|
34835
|
+
// SKILL.md, and docs/** alongside personal memory hits.
|
|
34836
|
+
"viking://resources/repos"
|
|
34686
34837
|
];
|
|
34687
34838
|
return includeArchived ? [...scopes, `${userBase}/durable/archived`, `${userBase}/handoffs/archived`, `${userBase}/incidents/archived`] : scopes;
|
|
34688
34839
|
}
|
|
@@ -34899,8 +35050,8 @@ async function requiredOpenVikingCli() {
|
|
|
34899
35050
|
async function firstExistingPath(paths) {
|
|
34900
35051
|
for (const path of paths) {
|
|
34901
35052
|
try {
|
|
34902
|
-
await (0,
|
|
34903
|
-
return await (0,
|
|
35053
|
+
await (0, import_promises4.access)(path);
|
|
35054
|
+
return await (0, import_promises4.realpath)(path);
|
|
34904
35055
|
} catch (_err) {
|
|
34905
35056
|
continue;
|
|
34906
35057
|
}
|