threadnote 0.6.1 → 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/dist/mcp_server.cjs +246 -95
- package/dist/threadnote.cjs +38 -14
- package/docs/agent-instructions.md +8 -0
- package/docs/index.html +185 -106
- package/package.json +1 -1
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
|
}
|
package/dist/threadnote.cjs
CHANGED
|
@@ -7118,6 +7118,15 @@ function uriSegment(value) {
|
|
|
7118
7118
|
const normalized = value.toLowerCase().replace(/[^a-z0-9._-]+/g, "-").replace(/^-+|-+$/g, "");
|
|
7119
7119
|
return normalized.length > 0 ? normalized : "unknown";
|
|
7120
7120
|
}
|
|
7121
|
+
async function inferProjectFromQuery(manifestPath, query) {
|
|
7122
|
+
try {
|
|
7123
|
+
const manifest = await readSeedManifest(manifestPath);
|
|
7124
|
+
const normalized = query.toLowerCase();
|
|
7125
|
+
return manifest.projects.find((project) => normalized.includes(project.name.toLowerCase()));
|
|
7126
|
+
} catch {
|
|
7127
|
+
return void 0;
|
|
7128
|
+
}
|
|
7129
|
+
}
|
|
7121
7130
|
async function readSeedManifest(path) {
|
|
7122
7131
|
const raw = await (0, import_promises3.readFile)(path, "utf8");
|
|
7123
7132
|
const loaded = jsYaml.load(raw);
|
|
@@ -7388,11 +7397,32 @@ async function runRecall(config, options) {
|
|
|
7388
7397
|
args.push("--node-limit", String(parsePositiveInteger(options.nodeLimit, "node limit")));
|
|
7389
7398
|
}
|
|
7390
7399
|
await maybeRun(options.dryRun === true, ov, withIdentity(config, args));
|
|
7400
|
+
await augmentRecallWithSeededResources(config, ov, options, inferredUri);
|
|
7391
7401
|
await printExactMemoryMatches(config, ov, options.query, {
|
|
7392
7402
|
dryRun: options.dryRun === true,
|
|
7393
7403
|
includeArchived: options.includeArchived === true
|
|
7394
7404
|
});
|
|
7395
7405
|
}
|
|
7406
|
+
async function augmentRecallWithSeededResources(config, ov, options, inferredUri) {
|
|
7407
|
+
if (options.uri || options.inferScope === false) {
|
|
7408
|
+
return;
|
|
7409
|
+
}
|
|
7410
|
+
const project = await inferProjectFromQuery(config.manifestPath, options.query);
|
|
7411
|
+
if (!project) {
|
|
7412
|
+
return;
|
|
7413
|
+
}
|
|
7414
|
+
const projectResourceUri = trimTrailingSlash(project.uri);
|
|
7415
|
+
if (!projectResourceUri.startsWith("viking://") || projectResourceUri === inferredUri) {
|
|
7416
|
+
return;
|
|
7417
|
+
}
|
|
7418
|
+
const args = ["search", options.query, "--uri", projectResourceUri];
|
|
7419
|
+
if (options.nodeLimit) {
|
|
7420
|
+
args.push("--node-limit", String(parsePositiveInteger(options.nodeLimit, "node limit")));
|
|
7421
|
+
}
|
|
7422
|
+
console.log(`
|
|
7423
|
+
Also searching seeded resources: ${projectResourceUri}`);
|
|
7424
|
+
await maybeRun(options.dryRun === true, ov, withIdentity(config, args));
|
|
7425
|
+
}
|
|
7396
7426
|
async function runRead(config, uri, options) {
|
|
7397
7427
|
assertVikingUri(uri);
|
|
7398
7428
|
const ov = await openVikingCliForMode(options.dryRun === true);
|
|
@@ -7511,21 +7541,11 @@ async function runImportPack(config, options) {
|
|
|
7511
7541
|
);
|
|
7512
7542
|
}
|
|
7513
7543
|
async function inferRecallUri(config, query) {
|
|
7514
|
-
|
|
7515
|
-
if (/\bskills?\b/.test(normalizedQuery)) {
|
|
7516
|
-
const project2 = await inferProjectFromQuery(config, normalizedQuery);
|
|
7517
|
-
return project2 ? `viking://resources/agent-skills/repo-local-${uriSegment(project2.name)}` : "viking://resources/agent-skills";
|
|
7518
|
-
}
|
|
7519
|
-
const project = await inferProjectFromQuery(config, normalizedQuery);
|
|
7520
|
-
return project ? trimTrailingSlash(project.uri) : void 0;
|
|
7521
|
-
}
|
|
7522
|
-
async function inferProjectFromQuery(config, normalizedQuery) {
|
|
7523
|
-
try {
|
|
7524
|
-
const manifest = await readSeedManifest(config.manifestPath);
|
|
7525
|
-
return manifest.projects.find((project) => normalizedQuery.includes(project.name.toLowerCase()));
|
|
7526
|
-
} catch (_err) {
|
|
7544
|
+
if (!/\bskills?\b/.test(query.toLowerCase())) {
|
|
7527
7545
|
return void 0;
|
|
7528
7546
|
}
|
|
7547
|
+
const project = await inferProjectFromQuery(config.manifestPath, query);
|
|
7548
|
+
return project ? `viking://resources/agent-skills/repo-local-${uriSegment(project.name)}` : "viking://resources/agent-skills";
|
|
7529
7549
|
}
|
|
7530
7550
|
async function printExactMemoryMatches(config, ov, query, options) {
|
|
7531
7551
|
const terms = exactRecallTerms(query);
|
|
@@ -7755,7 +7775,11 @@ function exactMemoryScopes(config, includeArchived) {
|
|
|
7755
7775
|
`${userBase}/incidents/active`,
|
|
7756
7776
|
`${userBase}/events`,
|
|
7757
7777
|
`${userBase}/shared`,
|
|
7758
|
-
`viking://agent/${uriSegment(config.agentId)}/memories
|
|
7778
|
+
`viking://agent/${uriSegment(config.agentId)}/memories`,
|
|
7779
|
+
// Seeded project resources (READMEs, AGENTS.md, SKILL.md, docs/**) live
|
|
7780
|
+
// under viking://resources/repos/<project>. Include them so an exact-term
|
|
7781
|
+
// grep in a recall surfaces matches in seeded guidance, not only memories.
|
|
7782
|
+
"viking://resources/repos"
|
|
7759
7783
|
];
|
|
7760
7784
|
return includeArchived ? [...scopes, `${userBase}/durable/archived`, `${userBase}/handoffs/archived`, `${userBase}/incidents/archived`] : scopes;
|
|
7761
7785
|
}
|
|
@@ -21,9 +21,17 @@ At the start of a non-trivial task, recall relevant context before making change
|
|
|
21
21
|
- the current repo and branch;
|
|
22
22
|
- recent handoffs;
|
|
23
23
|
- durable feature memories for the branch, feature name, project/topic, or issue;
|
|
24
|
+
- seeded project guidance under `viking://resources/repos/<project>` — README, AGENTS.md, CLAUDE.md, SKILL.md, docs/\*\*
|
|
25
|
+
— for canonical conventions, test commands, release process, on-call runbooks, and anything the team has checked into
|
|
26
|
+
the repo;
|
|
24
27
|
- task-specific skills or workflow guidance;
|
|
25
28
|
- user or team preferences that may affect the work.
|
|
26
29
|
|
|
30
|
+
Include the repo or project name as a token in the recall query so the project-guidance pass fires. `recall_context`
|
|
31
|
+
runs a parallel scoped search against `viking://resources/repos/<project>` whenever the query mentions a seeded project
|
|
32
|
+
name, so memories AND project documentation come back in the same response. Treat seeded resources as the canonical
|
|
33
|
+
source for "how does this repo do X" and personal memories as in-flight or per-author context layered on top.
|
|
34
|
+
|
|
27
35
|
When a recalled handoff describes an active branch or feature, do a second recall for durable memories about that
|
|
28
36
|
feature before coding. The handoff tells you the current work state; durable feature memories tell you the design,
|
|
29
37
|
decisions, invariants, interfaces, and gotchas that should survive beyond one session.
|
package/docs/index.html
CHANGED
|
@@ -78,7 +78,11 @@
|
|
|
78
78
|
body {
|
|
79
79
|
line-height: 1.55;
|
|
80
80
|
font-size: clamp(15px, 1.05vw, 17px);
|
|
81
|
-
overflow-x:
|
|
81
|
+
overflow-x: clip;
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
html {
|
|
85
|
+
overflow-x: clip;
|
|
82
86
|
}
|
|
83
87
|
|
|
84
88
|
::selection {
|
|
@@ -117,6 +121,10 @@
|
|
|
117
121
|
padding: 1em 1.2em;
|
|
118
122
|
overflow-x: auto;
|
|
119
123
|
line-height: 1.55;
|
|
124
|
+
/* Without this, a single long unbreakable line (curl URLs, etc.)
|
|
125
|
+
pushes the pre wider than its container and out of the viewport on
|
|
126
|
+
mobile. With it, the pre scrolls internally instead. */
|
|
127
|
+
max-width: 100%;
|
|
120
128
|
}
|
|
121
129
|
|
|
122
130
|
pre code {
|
|
@@ -148,6 +156,10 @@
|
|
|
148
156
|
display: flex;
|
|
149
157
|
flex-direction: column;
|
|
150
158
|
justify-content: center;
|
|
159
|
+
/* Default flex/block intrinsic min-width is the widest child, which
|
|
160
|
+
lets long unbreakable mono lines (URLs, MCP calls) make the inner
|
|
161
|
+
wider than its declared 100%. Hard-cap it. */
|
|
162
|
+
min-width: 0;
|
|
151
163
|
}
|
|
152
164
|
|
|
153
165
|
.slide-eyebrow {
|
|
@@ -222,29 +234,14 @@
|
|
|
222
234
|
align-items: flex-start;
|
|
223
235
|
}
|
|
224
236
|
|
|
225
|
-
.
|
|
226
|
-
position: fixed;
|
|
227
|
-
top: 1.5rem;
|
|
228
|
-
left: 1.5rem;
|
|
229
|
-
width: 220px;
|
|
230
|
-
height: 220px;
|
|
231
|
-
z-index: 40;
|
|
232
|
-
pointer-events: none;
|
|
233
|
-
}
|
|
234
|
-
|
|
235
|
-
.page-logo img {
|
|
236
|
-
width: 100%;
|
|
237
|
-
height: 100%;
|
|
237
|
+
.hero-logo {
|
|
238
238
|
display: block;
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
.
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
top: 1rem;
|
|
246
|
-
left: 1rem;
|
|
247
|
-
}
|
|
239
|
+
width: clamp(140px, 18vw, 220px);
|
|
240
|
+
height: auto;
|
|
241
|
+
margin-bottom: 1.5rem;
|
|
242
|
+
/* Override `#hero .slide-inner { align-items: flex-start }` for just
|
|
243
|
+
this child so the logo sits centered above the left-aligned title. */
|
|
244
|
+
align-self: center;
|
|
248
245
|
}
|
|
249
246
|
|
|
250
247
|
.hero-title-gradient {
|
|
@@ -504,35 +501,89 @@
|
|
|
504
501
|
border-radius: var(--radius-lg);
|
|
505
502
|
padding: 1.5rem;
|
|
506
503
|
display: grid;
|
|
507
|
-
gap: 0.
|
|
504
|
+
gap: 0.75rem;
|
|
508
505
|
transition: border-color 0.2s ease;
|
|
506
|
+
/* Let the grid track shrink to its container width rather than to the
|
|
507
|
+
widest mono child. Without this, long MCP call/response lines push
|
|
508
|
+
the grid item past viewport and the page scrolls horizontally on
|
|
509
|
+
mobile. */
|
|
510
|
+
min-width: 0;
|
|
509
511
|
}
|
|
510
512
|
|
|
511
513
|
.vignette:hover {
|
|
512
514
|
border-color: var(--accent-line);
|
|
513
515
|
}
|
|
514
516
|
|
|
515
|
-
.vignette .v-
|
|
517
|
+
.vignette .v-say {
|
|
516
518
|
font-style: italic;
|
|
519
|
+
font-size: 0.96rem;
|
|
517
520
|
color: var(--text);
|
|
518
|
-
|
|
521
|
+
background: rgba(255, 255, 255, 0.03);
|
|
522
|
+
border-left: 3px solid var(--accent);
|
|
523
|
+
border-radius: 4px;
|
|
524
|
+
padding: 0.55rem 0.85rem;
|
|
519
525
|
}
|
|
520
526
|
|
|
521
|
-
.vignette .v-
|
|
527
|
+
.vignette .v-auto {
|
|
528
|
+
font-style: italic;
|
|
522
529
|
color: var(--muted);
|
|
523
|
-
font-size: 0.95rem;
|
|
524
530
|
}
|
|
525
531
|
|
|
526
|
-
.vignette .v-
|
|
532
|
+
.vignette pre.v-call,
|
|
533
|
+
.vignette pre.v-response {
|
|
527
534
|
font-family: var(--font-mono);
|
|
528
|
-
font-size: 0.
|
|
529
|
-
color: var(--accent);
|
|
530
|
-
padding: 0.45rem 0.75rem;
|
|
535
|
+
font-size: 0.78rem;
|
|
531
536
|
background: var(--code-bg);
|
|
537
|
+
padding: 0.55rem 0.75rem;
|
|
532
538
|
border-radius: 6px;
|
|
533
539
|
border: 1px solid var(--line);
|
|
534
|
-
|
|
540
|
+
border-left: 1px solid var(--line);
|
|
541
|
+
white-space: pre-wrap;
|
|
542
|
+
word-break: break-word;
|
|
535
543
|
overflow-x: auto;
|
|
544
|
+
max-width: 100%;
|
|
545
|
+
line-height: 1.45;
|
|
546
|
+
margin: 0;
|
|
547
|
+
}
|
|
548
|
+
|
|
549
|
+
.vignette .v-call {
|
|
550
|
+
color: var(--accent);
|
|
551
|
+
}
|
|
552
|
+
|
|
553
|
+
.vignette .v-response {
|
|
554
|
+
color: var(--muted);
|
|
555
|
+
}
|
|
556
|
+
|
|
557
|
+
.vignette .v-say::before,
|
|
558
|
+
.vignette .v-call::before,
|
|
559
|
+
.vignette .v-response::before {
|
|
560
|
+
display: block;
|
|
561
|
+
font-family: var(--font-mono);
|
|
562
|
+
font-size: 0.68rem;
|
|
563
|
+
letter-spacing: 0.12em;
|
|
564
|
+
text-transform: uppercase;
|
|
565
|
+
color: var(--muted);
|
|
566
|
+
font-style: normal;
|
|
567
|
+
margin-bottom: 0.35rem;
|
|
568
|
+
}
|
|
569
|
+
|
|
570
|
+
.vignette .v-say::before {
|
|
571
|
+
content: 'You ask';
|
|
572
|
+
color: var(--accent);
|
|
573
|
+
}
|
|
574
|
+
|
|
575
|
+
.vignette .v-call::before {
|
|
576
|
+
content: 'Agent calls';
|
|
577
|
+
}
|
|
578
|
+
|
|
579
|
+
.vignette .v-response::before {
|
|
580
|
+
content: 'MCP returns';
|
|
581
|
+
}
|
|
582
|
+
|
|
583
|
+
.vignette .v-answer {
|
|
584
|
+
color: var(--muted);
|
|
585
|
+
font-size: 0.95rem;
|
|
586
|
+
margin-top: 0.25rem;
|
|
536
587
|
}
|
|
537
588
|
|
|
538
589
|
/* side nav dots */
|
|
@@ -695,10 +746,6 @@
|
|
|
695
746
|
<body>
|
|
696
747
|
<div class="progress"><div class="progress-bar" id="progressBar"></div></div>
|
|
697
748
|
|
|
698
|
-
<div class="page-logo" aria-hidden="true">
|
|
699
|
-
<img src="threadnote-logo-inverted.svg" alt="" />
|
|
700
|
-
</div>
|
|
701
|
-
|
|
702
749
|
<nav class="deck-nav" aria-label="Section progress">
|
|
703
750
|
<a href="#hero" data-label="Intro"><span>Intro</span></a>
|
|
704
751
|
<a href="#problem" data-label="Problem"><span>Problem</span></a>
|
|
@@ -710,8 +757,8 @@
|
|
|
710
757
|
<a href="#seed" data-label="Seed"><span>Seed</span></a>
|
|
711
758
|
<a href="#update" data-label="Update"><span>Update</span></a>
|
|
712
759
|
<a href="#lifecycle" data-label="Lifecycle"><span>Lifecycle</span></a>
|
|
713
|
-
<a href="#use-cases" data-label="Use cases"><span>Use cases</span></a>
|
|
714
760
|
<a href="#share" data-label="Share"><span>Share</span></a>
|
|
761
|
+
<a href="#use-cases" data-label="Use cases"><span>Use cases</span></a>
|
|
715
762
|
<a href="#close" data-label="Get started"><span>Start</span></a>
|
|
716
763
|
</nav>
|
|
717
764
|
|
|
@@ -719,6 +766,7 @@
|
|
|
719
766
|
<!-- 1. HERO -->
|
|
720
767
|
<section id="hero" class="slide">
|
|
721
768
|
<div class="slide-inner">
|
|
769
|
+
<img class="hero-logo" src="threadnote-logo-inverted.svg" alt="" aria-hidden="true" />
|
|
722
770
|
<h1 class="hero-title-gradient">
|
|
723
771
|
Shared local context<br />for <em class="hero-accent">development agents</em>.
|
|
724
772
|
</h1>
|
|
@@ -727,7 +775,7 @@
|
|
|
727
775
|
team-shared decisions — recallable from any session, on any agent, in any worktree.
|
|
728
776
|
</p>
|
|
729
777
|
<div class="cta-row">
|
|
730
|
-
<span class="pill">v0.
|
|
778
|
+
<span class="pill"><span id="version-tag">v0.6.1</span> · local-first · MIT</span>
|
|
731
779
|
</div>
|
|
732
780
|
</div>
|
|
733
781
|
<div class="scroll-cue">scroll ↓ · <span class="kbd">↓</span> / <span class="kbd">j</span></div>
|
|
@@ -1254,75 +1302,10 @@ threadnote remember \
|
|
|
1254
1302
|
</div>
|
|
1255
1303
|
</section>
|
|
1256
1304
|
|
|
1257
|
-
<!-- 11.
|
|
1258
|
-
<section id="use-cases" class="slide">
|
|
1259
|
-
<div class="slide-inner">
|
|
1260
|
-
<span class="slide-eyebrow">10 · Real-world use cases</span>
|
|
1261
|
-
<h2>Where it actually pays off.</h2>
|
|
1262
|
-
|
|
1263
|
-
<div class="vignettes">
|
|
1264
|
-
<div class="vignette reveal">
|
|
1265
|
-
<div class="v-prompt">"Continue the feature from yesterday."</div>
|
|
1266
|
-
<div class="v-answer">
|
|
1267
|
-
Fresh session, fresh agent. Threadnote recalls the active handoff for the branch plus the durable
|
|
1268
|
-
feature memory: design decisions, the open PR, what's left, what just failed in CI. No re-explaining.
|
|
1269
|
-
</div>
|
|
1270
|
-
<div class="v-cmd">threadnote recall --query "<branch> latest handoff"</div>
|
|
1271
|
-
</div>
|
|
1272
|
-
|
|
1273
|
-
<div class="vignette reveal">
|
|
1274
|
-
<div class="v-prompt">"What did we conclude about that prod-on-call thing?"</div>
|
|
1275
|
-
<div class="v-answer">
|
|
1276
|
-
The sanitized handoff from the on-call escalation is still there. Re-open the same Slack thread three
|
|
1277
|
-
weeks later; the durable feature memory points your agent at the right files and the resolution.
|
|
1278
|
-
</div>
|
|
1279
|
-
<div class="v-cmd">threadnote recall --query "export workflow timeout on-call findings"</div>
|
|
1280
|
-
</div>
|
|
1281
|
-
|
|
1282
|
-
<div class="vignette reveal">
|
|
1283
|
-
<div class="v-prompt">"Switch from Codex to Claude mid-task."</div>
|
|
1284
|
-
<div class="v-answer">
|
|
1285
|
-
Codex stores a handoff before pausing. Claude opens the same MCP layer, recalls the handoff plus the
|
|
1286
|
-
durable memory, picks up the test command and the open blocker without you typing them again.
|
|
1287
|
-
</div>
|
|
1288
|
-
<div class="v-cmd">threadnote handoff --task "..." --next-step "..."</div>
|
|
1289
|
-
</div>
|
|
1290
|
-
|
|
1291
|
-
<div class="vignette reveal">
|
|
1292
|
-
<div class="v-prompt">"Onboard a new teammate to a feature."</div>
|
|
1293
|
-
<div class="v-answer">
|
|
1294
|
-
Publish the feature's durable memory to the team repo with <code>share publish</code>. Their agent
|
|
1295
|
-
pulls it on the next <code>share sync</code> and recall surfaces it alongside their own personal
|
|
1296
|
-
memories — no DM forwarding, no PDF dumping.
|
|
1297
|
-
</div>
|
|
1298
|
-
<div class="v-cmd">threadnote share publish viking://user/<you>/memories/durable/...</div>
|
|
1299
|
-
</div>
|
|
1300
|
-
|
|
1301
|
-
<div class="vignette reveal">
|
|
1302
|
-
<div class="v-prompt">"Compaction is about to nuke the context."</div>
|
|
1303
|
-
<div class="v-answer">
|
|
1304
|
-
Before context fills up, the agent stores a handoff. The next turn after compaction recalls it — the
|
|
1305
|
-
summary covers the conversation arc, threadnote covers the durable facts the summary couldn't keep.
|
|
1306
|
-
</div>
|
|
1307
|
-
<div class="v-cmd">threadnote handoff --project <repo> --topic <feature> ...</div>
|
|
1308
|
-
</div>
|
|
1309
|
-
|
|
1310
|
-
<div class="vignette reveal">
|
|
1311
|
-
<div class="v-prompt">"Where does this repo run its release notes?"</div>
|
|
1312
|
-
<div class="v-answer">
|
|
1313
|
-
You discovered it the hard way last time. Tell your agent to <code>remember</code> the fact. Next time
|
|
1314
|
-
anyone asks, the agent finds it in two seconds without grepping the wiki.
|
|
1315
|
-
</div>
|
|
1316
|
-
<div class="v-cmd">threadnote remember --kind durable --topic release-process ...</div>
|
|
1317
|
-
</div>
|
|
1318
|
-
</div>
|
|
1319
|
-
</div>
|
|
1320
|
-
</section>
|
|
1321
|
-
|
|
1322
|
-
<!-- 12. SHARE -->
|
|
1305
|
+
<!-- 11. SHARE -->
|
|
1323
1306
|
<section id="share" class="slide">
|
|
1324
1307
|
<div class="slide-inner">
|
|
1325
|
-
<span class="slide-eyebrow">
|
|
1308
|
+
<span class="slide-eyebrow">10 · Sharing across the team</span>
|
|
1326
1309
|
<h2>Curated memories. Yours to push. Theirs to pull.</h2>
|
|
1327
1310
|
<p class="lead">
|
|
1328
1311
|
<code>threadnote share</code> publishes a subset of durable memories into a git repo so other engineers'
|
|
@@ -1409,6 +1392,81 @@ threadnote remember \
|
|
|
1409
1392
|
</div>
|
|
1410
1393
|
</section>
|
|
1411
1394
|
|
|
1395
|
+
<!-- 12. USE CASES -->
|
|
1396
|
+
<section id="use-cases" class="slide">
|
|
1397
|
+
<div class="slide-inner">
|
|
1398
|
+
<span class="slide-eyebrow">11 · Real-world use cases</span>
|
|
1399
|
+
<h2>What you say. What the agent does.</h2>
|
|
1400
|
+
<p class="lead">
|
|
1401
|
+
You don't run threadnote commands yourself. You talk to your agent the way you already do; the agent calls
|
|
1402
|
+
the MCP layer and the memory stays in sync across sessions, agents, and teammates.
|
|
1403
|
+
</p>
|
|
1404
|
+
|
|
1405
|
+
<div class="vignettes">
|
|
1406
|
+
<div class="vignette reveal">
|
|
1407
|
+
<div class="v-say">Continue where we left off on this branch.</div>
|
|
1408
|
+
<pre class="v-call">recall_context({query: "<branch> latest handoff durable feature memory"})</pre>
|
|
1409
|
+
<pre class="v-response">→ viking://user/you/memories/handoffs/active/<branch>/auto-precompact.md
|
|
1410
|
+
→ viking://user/you/memories/durable/projects/<repo>/<feature>.md</pre>
|
|
1411
|
+
<div class="v-answer">
|
|
1412
|
+
Fresh session, fresh agent. It reads both pointers, picks up the design decisions, the open PR, what's
|
|
1413
|
+
left to test, and what just failed in CI. No re-explaining.
|
|
1414
|
+
</div>
|
|
1415
|
+
</div>
|
|
1416
|
+
|
|
1417
|
+
<div class="vignette reveal">
|
|
1418
|
+
<div class="v-say">Save where we are — I'm switching to another agent.</div>
|
|
1419
|
+
<pre class="v-call">remember_context({kind: "handoff", project: "<repo>", topic: "<feature>", text: "..."})</pre>
|
|
1420
|
+
<pre class="v-response">Stored: viking://user/you/memories/handoffs/active/<repo>/<feature>.md</pre>
|
|
1421
|
+
<div class="v-answer">
|
|
1422
|
+
Open the next agent — the SessionStart hook recalls the same handoff and the durable memory. It picks
|
|
1423
|
+
up the test command and the open blocker without you retyping them.
|
|
1424
|
+
</div>
|
|
1425
|
+
</div>
|
|
1426
|
+
|
|
1427
|
+
<div class="vignette reveal">
|
|
1428
|
+
<div class="v-say">Share this feature's design memory with the team.</div>
|
|
1429
|
+
<pre class="v-call">share_publish({uri: "viking://user/you/memories/durable/projects/<repo>/<feature>.md"})</pre>
|
|
1430
|
+
<pre class="v-response">Published → shared/<team>/durable/projects/<repo>/<feature>.md
|
|
1431
|
+
git push: ok</pre>
|
|
1432
|
+
<div class="v-answer">
|
|
1433
|
+
The memory lands in the team git repo. A teammate's next <code>share sync</code> pulls it; their
|
|
1434
|
+
recall surfaces it alongside their own memories — no DM forwarding, no PDF dumping.
|
|
1435
|
+
</div>
|
|
1436
|
+
</div>
|
|
1437
|
+
|
|
1438
|
+
<div class="vignette reveal">
|
|
1439
|
+
<div class="v-say v-auto">(automatic — the PreCompact hook fires before context compaction)</div>
|
|
1440
|
+
<pre class="v-call">remember_context({kind: "handoff", project: "<repo>", topic: "auto-precompact", text: "..."})</pre>
|
|
1441
|
+
<pre class="v-response">Stored: viking://user/you/memories/handoffs/active/<repo>/auto-precompact.md</pre>
|
|
1442
|
+
<div class="v-answer">
|
|
1443
|
+
Compaction summarizes the conversation arc but loses the durable facts. Threadnote's hook captures them
|
|
1444
|
+
first; the post-compaction turn recalls them and resumes without amnesia.
|
|
1445
|
+
</div>
|
|
1446
|
+
</div>
|
|
1447
|
+
|
|
1448
|
+
<div class="vignette reveal">
|
|
1449
|
+
<div class="v-say">Remember that this repo cuts release notes from CI via gh release create.</div>
|
|
1450
|
+
<pre class="v-call">remember_context({kind: "durable", project: "<repo>", topic: "release-process", text: "..."})</pre>
|
|
1451
|
+
<pre class="v-response">Stored: viking://user/you/memories/durable/projects/<repo>/release-process.md</pre>
|
|
1452
|
+
<div class="v-answer">
|
|
1453
|
+
Next time anyone asks where releases happen, the agent finds it in two seconds. No grepping the wiki.
|
|
1454
|
+
</div>
|
|
1455
|
+
</div>
|
|
1456
|
+
|
|
1457
|
+
<div class="vignette reveal">
|
|
1458
|
+
<div class="v-say">What did we conclude about the export-workflow timeout?</div>
|
|
1459
|
+
<pre class="v-call">recall_context({query: "export workflow timeout findings"})</pre>
|
|
1460
|
+
<pre class="v-response">→ viking://user/you/memories/durable/projects/<repo>/export-timeout.md (score 0.71)</pre>
|
|
1461
|
+
<div class="v-answer">
|
|
1462
|
+
Re-open the same investigation three weeks later; the durable memory points your agent at the right
|
|
1463
|
+
files and the resolution. The trail doesn't decay.
|
|
1464
|
+
</div>
|
|
1465
|
+
</div>
|
|
1466
|
+
</div>
|
|
1467
|
+
</div>
|
|
1468
|
+
</section>
|
|
1469
|
+
|
|
1412
1470
|
<!-- 13. CLOSE -->
|
|
1413
1471
|
<section id="close" class="slide">
|
|
1414
1472
|
<div class="slide-inner">
|
|
@@ -1538,6 +1596,27 @@ threadnote doctor --dry-run</code></pre>
|
|
|
1538
1596
|
}
|
|
1539
1597
|
});
|
|
1540
1598
|
})();
|
|
1599
|
+
|
|
1600
|
+
// Replace the hardcoded version pill with whatever npm currently
|
|
1601
|
+
// serves as latest. Fails silently and keeps the fallback text on any
|
|
1602
|
+
// network or registry error — the deck must render even offline.
|
|
1603
|
+
(async () => {
|
|
1604
|
+
const tag = document.getElementById('version-tag');
|
|
1605
|
+
if (!tag) return;
|
|
1606
|
+
try {
|
|
1607
|
+
const response = await fetch('https://registry.npmjs.org/threadnote/latest', {
|
|
1608
|
+
headers: {accept: 'application/json'},
|
|
1609
|
+
signal: AbortSignal.timeout(3000),
|
|
1610
|
+
});
|
|
1611
|
+
if (!response.ok) return;
|
|
1612
|
+
const data = await response.json();
|
|
1613
|
+
if (typeof data.version === 'string' && data.version.length > 0) {
|
|
1614
|
+
tag.textContent = 'v' + data.version;
|
|
1615
|
+
}
|
|
1616
|
+
} catch {
|
|
1617
|
+
// Keep the static fallback.
|
|
1618
|
+
}
|
|
1619
|
+
})();
|
|
1541
1620
|
</script>
|
|
1542
1621
|
</body>
|
|
1543
1622
|
</html>
|