@productbrain/mcp 0.0.1-beta.9574 → 0.0.1-beta.9584
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.
|
@@ -395,7 +395,7 @@ var TOOL_NAME_MIGRATIONS = /* @__PURE__ */ new Map([
|
|
|
395
395
|
["search", 'context action="search"'],
|
|
396
396
|
["relate-entries", 'relations action="create"'],
|
|
397
397
|
["batch-relate", 'relations action="batch-create"'],
|
|
398
|
-
["find-related", '
|
|
398
|
+
["find-related", 'context action="find"'],
|
|
399
399
|
["gather-context", 'context action="gather"'],
|
|
400
400
|
["get-build-context", 'context action="build"'],
|
|
401
401
|
["list-collections", 'collections action="list"'],
|
|
@@ -443,7 +443,7 @@ var TOOL_NAME_MIGRATIONS = /* @__PURE__ */ new Map([
|
|
|
443
443
|
["move-entry", 'entries action="move"'],
|
|
444
444
|
["session-wrapup", 'session action="wrapup-review|wrapup-commit" (review \u2192 wrapup-review, commit-all \u2192 wrapup-commit)'],
|
|
445
445
|
["facilitate", 'session action="resume|commit-constellation" (same action names)'],
|
|
446
|
-
["graph", '
|
|
446
|
+
["graph", 'context action="find" (same action name)'],
|
|
447
447
|
["documents", 'context action="last-verified-brief"'],
|
|
448
448
|
["labels", 'collections action="label-*" (list/create/update/delete/apply/remove \u2192 label-<same>)'],
|
|
449
449
|
["health", 'workspace action="check|whoami|status|audit|self-test" (same action names)'],
|
|
@@ -2054,7 +2054,7 @@ Use \`context action=get\` to inspect the existing entry, or \`entries action=up
|
|
|
2054
2054
|
}
|
|
2055
2055
|
const next = [];
|
|
2056
2056
|
if (finalStatus === "committed") {
|
|
2057
|
-
next.push({ tool: "
|
|
2057
|
+
next.push({ tool: "context", description: "See its connections", parameters: { action: "find", entryId: finalEntryId } });
|
|
2058
2058
|
} else {
|
|
2059
2059
|
next.push({ tool: "context", description: "Review the draft alternative", parameters: { action: "get", entryId: finalEntryId } });
|
|
2060
2060
|
next.push({ tool: "entries", description: "Accept onto Chain", parameters: { action: "commit", entryId: finalEntryId } });
|
|
@@ -2653,7 +2653,7 @@ No DB writes \u2014 call without \`preview:true\` to accept for real.` }],
|
|
|
2653
2653
|
} catch {
|
|
2654
2654
|
}
|
|
2655
2655
|
const next = [
|
|
2656
|
-
{ tool: "
|
|
2656
|
+
{ tool: "context", description: "See its connections", parameters: { action: "find", entryId } },
|
|
2657
2657
|
{ tool: "context", description: "View accepted entry", parameters: { action: "get", entryId } }
|
|
2658
2658
|
];
|
|
2659
2659
|
const summary = `Accepted ${entryId} (${entry.name}) onto the Chain.`;
|
|
@@ -3543,7 +3543,7 @@ async function handleGet(entryId) {
|
|
|
3543
3543
|
`Found ${entryId}: ${String(e.name)} [${String(e.collectionSlug ?? "")}].`,
|
|
3544
3544
|
entryData,
|
|
3545
3545
|
[
|
|
3546
|
-
{ tool: "
|
|
3546
|
+
{ tool: "context", description: "See its connections", parameters: { action: "find", entryId } },
|
|
3547
3547
|
{ tool: "context", description: "Gather related context", parameters: { action: "gather", entryId } }
|
|
3548
3548
|
]
|
|
3549
3549
|
)
|
|
@@ -3791,194 +3791,64 @@ ${footer}` }],
|
|
|
3791
3791
|
}
|
|
3792
3792
|
|
|
3793
3793
|
// src/tools/relations.ts
|
|
3794
|
-
import { z as z13 } from "zod/v3";
|
|
3795
|
-
|
|
3796
|
-
// src/tools/graph.ts
|
|
3797
3794
|
import { z as z12 } from "zod/v3";
|
|
3798
|
-
var
|
|
3799
|
-
var
|
|
3800
|
-
|
|
3801
|
-
|
|
3802
|
-
)
|
|
3803
|
-
entryId: z12.string().max(200).describe("Entry ID, e.g. '<PREFIX>-<n>'"),
|
|
3804
|
-
direction: z12.enum(["incoming", "outgoing", "both"]).default("both").optional().describe("For find: 'incoming' = what references this, 'outgoing' = what this references")
|
|
3805
|
-
});
|
|
3806
|
-
var graphFindOutputSchema = z12.object({
|
|
3807
|
-
entryId: z12.string(),
|
|
3808
|
-
relations: z12.array(z12.object({
|
|
3809
|
-
entryId: z12.string().optional(),
|
|
3810
|
-
name: z12.string(),
|
|
3811
|
-
type: z12.string(),
|
|
3812
|
-
direction: z12.enum(["outgoing", "incoming"])
|
|
3813
|
-
})),
|
|
3814
|
-
total: z12.number()
|
|
3795
|
+
var RELATIONS_ACTIONS = ["create", "batch-create", "delete"];
|
|
3796
|
+
var relationItemSchema = z12.object({
|
|
3797
|
+
from: z12.string().max(200),
|
|
3798
|
+
to: z12.string().max(200),
|
|
3799
|
+
type: z12.string().max(200)
|
|
3815
3800
|
});
|
|
3816
|
-
|
|
3817
|
-
|
|
3818
|
-
|
|
3819
|
-
return {
|
|
3820
|
-
content: [{ type: "text", text: `No relations found for \`${entryId}\`. Use relations action=create to create connections.` }],
|
|
3821
|
-
structuredContent: success(
|
|
3822
|
-
`No relations found for '${entryId}'.`,
|
|
3823
|
-
{ entryId, relations: [], nodes: [], edges: [], total: 0 },
|
|
3824
|
-
[
|
|
3825
|
-
{ tool: "relations", description: "Create a relation", parameters: { action: "create", from: entryId } }
|
|
3826
|
-
]
|
|
3827
|
-
)
|
|
3828
|
-
};
|
|
3829
|
-
}
|
|
3830
|
-
const sourceEntry = await kernelQuery("chain.getEntry", { entryId });
|
|
3831
|
-
if (!sourceEntry) {
|
|
3832
|
-
return notFoundResult(entryId, `Entry '${entryId}' not found. Try context action=search to find the right ID.`);
|
|
3833
|
-
}
|
|
3834
|
-
const sourceInternalId = sourceEntry._id;
|
|
3835
|
-
const MAX_RELATIONS = 25;
|
|
3836
|
-
const truncated = relations.length > MAX_RELATIONS;
|
|
3837
|
-
const capped = relations.slice(0, MAX_RELATIONS);
|
|
3838
|
-
const otherIds = /* @__PURE__ */ new Set();
|
|
3839
|
-
for (const r of capped) {
|
|
3840
|
-
const otherId = r.fromId === sourceInternalId ? r.toId : r.fromId;
|
|
3841
|
-
otherIds.add(otherId);
|
|
3842
|
-
}
|
|
3843
|
-
const otherEntries = /* @__PURE__ */ new Map();
|
|
3844
|
-
for (const id of otherIds) {
|
|
3845
|
-
const entry = await kernelQuery("chain.getEntry", { id });
|
|
3846
|
-
if (entry) {
|
|
3847
|
-
otherEntries.set(entry._id, { entryId: entry.entryId, name: entry.name, collectionId: entry.collectionId });
|
|
3848
|
-
}
|
|
3849
|
-
}
|
|
3850
|
-
const collections = await kernelQuery("chain.listCollections");
|
|
3851
|
-
const collMap = /* @__PURE__ */ new Map();
|
|
3852
|
-
for (const c of collections) collMap.set(c._id, c.slug);
|
|
3853
|
-
const lines = [`## Find Result`, "", `# Relations for ${entryId}: ${sourceEntry.name}`, ""];
|
|
3854
|
-
const enriched = capped.map((r) => {
|
|
3855
|
-
const isOutgoing = r.fromId === sourceInternalId;
|
|
3856
|
-
const otherId = isOutgoing ? r.toId : r.fromId;
|
|
3857
|
-
const other = otherEntries.get(otherId);
|
|
3858
|
-
const otherLabel = other?.entryId ? `${other.entryId}: ${other.name}` : other?.name ?? "(deleted)";
|
|
3859
|
-
const colSlug = other ? collMap.get(other.collectionId) ?? "unknown" : "unknown";
|
|
3860
|
-
return { isOutgoing, type: r.type, otherLabel, colSlug, otherEntryId: other?.entryId, otherName: other?.name ?? "(deleted)" };
|
|
3861
|
-
});
|
|
3862
|
-
const outgoing = enriched.filter((r) => r.isOutgoing);
|
|
3863
|
-
const incoming = enriched.filter((r) => !r.isOutgoing);
|
|
3864
|
-
if ((direction === "outgoing" || direction === "both") && outgoing.length > 0) {
|
|
3865
|
-
lines.push(`## Outgoing (${outgoing.length})`);
|
|
3866
|
-
for (const r of outgoing) {
|
|
3867
|
-
lines.push(`- \u2192 **${r.type}** ${r.otherLabel} [${r.colSlug}]`);
|
|
3868
|
-
}
|
|
3869
|
-
lines.push("");
|
|
3870
|
-
}
|
|
3871
|
-
if ((direction === "incoming" || direction === "both") && incoming.length > 0) {
|
|
3872
|
-
lines.push(`## Incoming (${incoming.length})`);
|
|
3873
|
-
for (const r of incoming) {
|
|
3874
|
-
lines.push(`- \u2190 **${r.type}** ${r.otherLabel} [${r.colSlug}]`);
|
|
3875
|
-
}
|
|
3876
|
-
lines.push("");
|
|
3877
|
-
}
|
|
3878
|
-
const shown = direction === "outgoing" ? outgoing : direction === "incoming" ? incoming : enriched;
|
|
3879
|
-
if (shown.length === 0) {
|
|
3880
|
-
lines.push(`No ${direction} relations found for \`${entryId}\`.`);
|
|
3881
|
-
}
|
|
3882
|
-
if (truncated) {
|
|
3883
|
-
lines.push(`_Showing first ${MAX_RELATIONS} of ${relations.length} relations. Use context action=get for the full picture._`);
|
|
3884
|
-
}
|
|
3885
|
-
const structuredRelations = shown.map((r) => ({
|
|
3886
|
-
entryId: r.otherEntryId,
|
|
3887
|
-
name: r.otherName,
|
|
3888
|
-
type: r.type,
|
|
3889
|
-
direction: r.isOutgoing ? "outgoing" : "incoming"
|
|
3890
|
-
}));
|
|
3891
|
-
const nodes = [
|
|
3892
|
-
{ id: entryId, name: sourceEntry.name, collectionName: "" },
|
|
3893
|
-
...shown.map((r) => ({
|
|
3894
|
-
id: r.otherEntryId ?? String(Math.random()),
|
|
3895
|
-
name: r.otherName,
|
|
3896
|
-
collectionName: r.colSlug
|
|
3897
|
-
}))
|
|
3898
|
-
];
|
|
3899
|
-
const edges = shown.filter((r) => r.otherEntryId).map((r) => ({
|
|
3900
|
-
source: entryId,
|
|
3901
|
-
target: r.otherEntryId,
|
|
3902
|
-
type: r.type
|
|
3903
|
-
}));
|
|
3904
|
-
return {
|
|
3905
|
-
content: [{ type: "text", text: lines.join("\n") }],
|
|
3906
|
-
structuredContent: success(
|
|
3907
|
-
`Found ${relations.length} relations for ${entryId}.`,
|
|
3908
|
-
{ entryId, relations: structuredRelations, nodes, edges, total: relations.length },
|
|
3909
|
-
[{ tool: "context", description: "View entry details", parameters: { action: "get", entryId } }]
|
|
3910
|
-
)
|
|
3911
|
-
};
|
|
3912
|
-
}
|
|
3913
|
-
|
|
3914
|
-
// src/tools/relations.ts
|
|
3915
|
-
var RELATIONS_ACTIONS = ["create", "batch-create", "delete", "find"];
|
|
3916
|
-
var relationItemSchema = z13.object({
|
|
3917
|
-
from: z13.string().max(200),
|
|
3918
|
-
to: z13.string().max(200),
|
|
3919
|
-
type: z13.string().max(200)
|
|
3920
|
-
});
|
|
3921
|
-
var relationsSchema = z13.object({
|
|
3922
|
-
action: z13.enum(RELATIONS_ACTIONS).describe(
|
|
3923
|
-
"'create': link two entries. 'batch-create': create multiple relations (validates every item before writing any). 'delete': remove a relation. 'find': traverse relations from an entry (graph walk, absorbs graph action=find)."
|
|
3801
|
+
var relationsSchema = z12.object({
|
|
3802
|
+
action: z12.enum(RELATIONS_ACTIONS).describe(
|
|
3803
|
+
"'create': link two entries. 'batch-create': create multiple relations (validates every item before writing any). 'delete': remove a relation."
|
|
3924
3804
|
),
|
|
3925
|
-
from:
|
|
3926
|
-
to:
|
|
3927
|
-
type:
|
|
3928
|
-
relations:
|
|
3805
|
+
from: z12.string().max(200).optional().describe("For 'create'/'delete': source entry ID."),
|
|
3806
|
+
to: z12.string().max(200).optional().describe("For 'create'/'delete': target entry ID."),
|
|
3807
|
+
type: z12.string().max(200).optional().describe("For 'create'/'delete': relation type."),
|
|
3808
|
+
relations: z12.array(relationItemSchema).min(1).max(RELATION_BATCH_MAX).optional().describe("For 'batch-create': array of {from, to, type}."),
|
|
3929
3809
|
// WP-316 S3: Preview gate — dry-run mode for action=create.
|
|
3930
|
-
preview:
|
|
3931
|
-
entryId: z13.string().max(200).optional().describe("For 'find': entry ID, e.g. '<PREFIX>-<n>'."),
|
|
3932
|
-
direction: z13.enum(["incoming", "outgoing", "both"]).optional().describe("For 'find': 'incoming' = what references this, 'outgoing' = what this references. Default 'both'.")
|
|
3810
|
+
preview: z12.boolean().optional().describe("For 'create': if true, validates the relation without writing. Returns what would happen. Default false.")
|
|
3933
3811
|
});
|
|
3934
|
-
var relationsCreateVariant =
|
|
3935
|
-
action:
|
|
3936
|
-
from:
|
|
3937
|
-
to:
|
|
3938
|
-
type:
|
|
3939
|
-
preview:
|
|
3812
|
+
var relationsCreateVariant = z12.object({
|
|
3813
|
+
action: z12.literal("create"),
|
|
3814
|
+
from: z12.string().max(200),
|
|
3815
|
+
to: z12.string().max(200),
|
|
3816
|
+
type: z12.string().max(200),
|
|
3817
|
+
preview: z12.boolean().optional()
|
|
3940
3818
|
});
|
|
3941
|
-
var relationsBatchCreateVariant =
|
|
3942
|
-
action:
|
|
3943
|
-
relations:
|
|
3819
|
+
var relationsBatchCreateVariant = z12.object({
|
|
3820
|
+
action: z12.literal("batch-create"),
|
|
3821
|
+
relations: z12.array(relationItemSchema).min(1).max(RELATION_BATCH_MAX)
|
|
3944
3822
|
});
|
|
3945
|
-
var relationsDeleteVariant =
|
|
3946
|
-
action:
|
|
3947
|
-
from:
|
|
3948
|
-
to:
|
|
3949
|
-
type:
|
|
3823
|
+
var relationsDeleteVariant = z12.object({
|
|
3824
|
+
action: z12.literal("delete"),
|
|
3825
|
+
from: z12.string().max(200),
|
|
3826
|
+
to: z12.string().max(200),
|
|
3827
|
+
type: z12.string().max(200)
|
|
3950
3828
|
});
|
|
3951
|
-
var
|
|
3952
|
-
action: z13.literal("find"),
|
|
3953
|
-
entryId: z13.string().max(200),
|
|
3954
|
-
direction: z13.enum(["incoming", "outgoing", "both"]).optional().default("both")
|
|
3955
|
-
});
|
|
3956
|
-
var relationsActionUnion = z13.discriminatedUnion("action", [
|
|
3829
|
+
var relationsActionUnion = z12.discriminatedUnion("action", [
|
|
3957
3830
|
relationsCreateVariant,
|
|
3958
3831
|
relationsBatchCreateVariant,
|
|
3959
|
-
relationsDeleteVariant
|
|
3960
|
-
relationsFindVariant
|
|
3832
|
+
relationsDeleteVariant
|
|
3961
3833
|
]);
|
|
3962
3834
|
var RELATIONS_ACTION_SPECS = {
|
|
3963
3835
|
create: { params: ["from", "to", "type", "preview"], description: "from, to, and type are required." },
|
|
3964
3836
|
"batch-create": { params: ["relations"], description: "relations (1-20 items) is required." },
|
|
3965
|
-
delete: { params: ["from", "to", "type"], description: "from, to, and type are required." }
|
|
3966
|
-
find: { params: ["entryId", "direction"], description: "entryId is required." }
|
|
3837
|
+
delete: { params: ["from", "to", "type"], description: "from, to, and type are required." }
|
|
3967
3838
|
};
|
|
3968
3839
|
function registerRelationsTools(server) {
|
|
3969
3840
|
const relationsHandlers = {
|
|
3970
3841
|
create: (data) => handleCreate(data.from, data.to, data.type, data.preview),
|
|
3971
3842
|
"batch-create": (data) => handleBatchCreate(data.relations),
|
|
3972
|
-
delete: (data) => handleDelete(data.from, data.to, data.type)
|
|
3973
|
-
find: (data) => handleFind(data.entryId, data.direction ?? "both")
|
|
3843
|
+
delete: (data) => handleDelete(data.from, data.to, data.type)
|
|
3974
3844
|
};
|
|
3975
3845
|
server.registerTool(
|
|
3976
3846
|
"relations",
|
|
3977
3847
|
{
|
|
3978
3848
|
title: "Relations",
|
|
3979
|
-
description: "Create
|
|
3849
|
+
description: "Create and delete relations between entries. A relation is written only when you ask for it, with exactly the verb you send. Read an entry's relations with the read-only `context action=find`.\n\n- **create**: Link two entries with a typed relation.\n- **batch-create**: Create multiple relations in one call (validates every item before writing any).\n- **delete**: Remove a relation by from, to, and type.",
|
|
3980
3850
|
inputSchema: relationsSchema,
|
|
3981
|
-
//
|
|
3851
|
+
// DEC-1894 / DEC-74: find lives on the read-only `context` tool; every action left here writes.
|
|
3982
3852
|
annotations: { readOnlyHint: false, destructiveHint: true, openWorldHint: false }
|
|
3983
3853
|
},
|
|
3984
3854
|
thinWrapper(async (args) => {
|
|
@@ -4107,7 +3977,7 @@ No DB writes \u2014 call without \`preview:true\` to create for real.${advisoryT
|
|
|
4107
3977
|
type,
|
|
4108
3978
|
...result2?.advisories && result2.advisories.length > 0 && { advisories: result2.advisories }
|
|
4109
3979
|
},
|
|
4110
|
-
[{ tool: "
|
|
3980
|
+
[{ tool: "context", description: "See connections", parameters: { action: "find", entryId: from } }]
|
|
4111
3981
|
)
|
|
4112
3982
|
};
|
|
4113
3983
|
}
|
|
@@ -4152,7 +4022,7 @@ async function handleBatchCreate(relations) {
|
|
|
4152
4022
|
lines.push(`- **${r.from}** \u2192 **${r.to}** (${r.type}): _${r.error}_`);
|
|
4153
4023
|
}
|
|
4154
4024
|
}
|
|
4155
|
-
const next = failed.length === 0 ? [{ tool: "
|
|
4025
|
+
const next = failed.length === 0 ? [{ tool: "context", description: "Check connections", parameters: { action: "find", entryId: relations[0].from } }] : [];
|
|
4156
4026
|
return {
|
|
4157
4027
|
content: [{ type: "text", text: lines.join("\n") }],
|
|
4158
4028
|
structuredContent: success(
|
|
@@ -4173,7 +4043,7 @@ async function handleDelete(from, to, type) {
|
|
|
4173
4043
|
`Removed relation: ${from} \u2014[${type}]\u2192 ${to}.`,
|
|
4174
4044
|
`Relation ${from} \u2192 ${to} (${type}) removed.`,
|
|
4175
4045
|
{ from, to, type },
|
|
4176
|
-
[{ tool: "
|
|
4046
|
+
[{ tool: "context", description: "Check remaining connections", parameters: { action: "find", entryId: from } }]
|
|
4177
4047
|
);
|
|
4178
4048
|
}
|
|
4179
4049
|
|
|
@@ -4181,16 +4051,16 @@ async function handleDelete(from, to, type) {
|
|
|
4181
4051
|
import { z as z16 } from "zod/v3";
|
|
4182
4052
|
|
|
4183
4053
|
// src/tools/documents.ts
|
|
4184
|
-
import { z as
|
|
4054
|
+
import { z as z13 } from "zod/v3";
|
|
4185
4055
|
var DOCUMENTS_ACTIONS = ["get-last-verified-brief"];
|
|
4186
|
-
var documentsSchema =
|
|
4187
|
-
action:
|
|
4056
|
+
var documentsSchema = z13.object({
|
|
4057
|
+
action: z13.enum(DOCUMENTS_ACTIONS).describe(
|
|
4188
4058
|
"'get-last-verified-brief': fetch the most recent verified brief snapshot for a (templateId, scopeKey) pair. Returns the verified summary so agents can build delta narratives ('since you last verified, X workstreams advanced')."
|
|
4189
4059
|
),
|
|
4190
|
-
templateId:
|
|
4060
|
+
templateId: z13.string().max(200).describe(
|
|
4191
4061
|
"Brief template identifier \u2014 currently 'steering-brief' is the only registered template."
|
|
4192
4062
|
),
|
|
4193
|
-
scopeKey:
|
|
4063
|
+
scopeKey: z13.string().max(200).describe(
|
|
4194
4064
|
"Canonical scope key. Use 'workspace:<workspaceId>' for the full workspace brief, or 'initiative:<INI-ID>' for an initiative-scoped brief. The same formula is applied at write time (chainwork/docKernel/scopeKey.ts), so passing the wrong shape returns exists:false."
|
|
4195
4065
|
)
|
|
4196
4066
|
});
|
|
@@ -4260,9 +4130,130 @@ This row pre-dates snapshot capture (legacy backfill), so no summary is availabl
|
|
|
4260
4130
|
|
|
4261
4131
|
// src/tools/contextLookups.ts
|
|
4262
4132
|
import { z as z15 } from "zod/v3";
|
|
4133
|
+
|
|
4134
|
+
// src/tools/graph.ts
|
|
4135
|
+
import { z as z14 } from "zod/v3";
|
|
4136
|
+
var GRAPH_ACTIONS = ["find"];
|
|
4137
|
+
var graphSchema = z14.object({
|
|
4138
|
+
action: z14.enum(GRAPH_ACTIONS).describe(
|
|
4139
|
+
"'find': traverse relations from an entry (graph walk)."
|
|
4140
|
+
),
|
|
4141
|
+
entryId: z14.string().max(200).describe("Entry ID, e.g. '<PREFIX>-<n>'"),
|
|
4142
|
+
direction: z14.enum(["incoming", "outgoing", "both"]).default("both").optional().describe("For find: 'incoming' = what references this, 'outgoing' = what this references")
|
|
4143
|
+
});
|
|
4144
|
+
var graphFindOutputSchema = z14.object({
|
|
4145
|
+
entryId: z14.string(),
|
|
4146
|
+
relations: z14.array(z14.object({
|
|
4147
|
+
entryId: z14.string().optional(),
|
|
4148
|
+
name: z14.string(),
|
|
4149
|
+
type: z14.string(),
|
|
4150
|
+
direction: z14.enum(["outgoing", "incoming"])
|
|
4151
|
+
})),
|
|
4152
|
+
total: z14.number()
|
|
4153
|
+
});
|
|
4154
|
+
async function handleFind(entryId, direction) {
|
|
4155
|
+
const relations = await kernelQuery("chain.listEntryRelations", { entryId });
|
|
4156
|
+
if (relations.length === 0) {
|
|
4157
|
+
return {
|
|
4158
|
+
content: [{ type: "text", text: `No relations found for \`${entryId}\`. Use relations action=create to create connections.` }],
|
|
4159
|
+
structuredContent: success(
|
|
4160
|
+
`No relations found for '${entryId}'.`,
|
|
4161
|
+
{ entryId, relations: [], nodes: [], edges: [], total: 0 },
|
|
4162
|
+
[
|
|
4163
|
+
{ tool: "relations", description: "Create a relation", parameters: { action: "create", from: entryId } }
|
|
4164
|
+
]
|
|
4165
|
+
)
|
|
4166
|
+
};
|
|
4167
|
+
}
|
|
4168
|
+
const sourceEntry = await kernelQuery("chain.getEntry", { entryId });
|
|
4169
|
+
if (!sourceEntry) {
|
|
4170
|
+
return notFoundResult(entryId, `Entry '${entryId}' not found. Try context action=search to find the right ID.`);
|
|
4171
|
+
}
|
|
4172
|
+
const sourceInternalId = sourceEntry._id;
|
|
4173
|
+
const MAX_RELATIONS = 25;
|
|
4174
|
+
const truncated = relations.length > MAX_RELATIONS;
|
|
4175
|
+
const capped = relations.slice(0, MAX_RELATIONS);
|
|
4176
|
+
const otherIds = /* @__PURE__ */ new Set();
|
|
4177
|
+
for (const r of capped) {
|
|
4178
|
+
const otherId = r.fromId === sourceInternalId ? r.toId : r.fromId;
|
|
4179
|
+
otherIds.add(otherId);
|
|
4180
|
+
}
|
|
4181
|
+
const otherEntries = /* @__PURE__ */ new Map();
|
|
4182
|
+
for (const id of otherIds) {
|
|
4183
|
+
const entry = await kernelQuery("chain.getEntry", { id });
|
|
4184
|
+
if (entry) {
|
|
4185
|
+
otherEntries.set(entry._id, { entryId: entry.entryId, name: entry.name, collectionId: entry.collectionId });
|
|
4186
|
+
}
|
|
4187
|
+
}
|
|
4188
|
+
const collections = await kernelQuery("chain.listCollections");
|
|
4189
|
+
const collMap = /* @__PURE__ */ new Map();
|
|
4190
|
+
for (const c of collections) collMap.set(c._id, c.slug);
|
|
4191
|
+
const lines = [`## Find Result`, "", `# Relations for ${entryId}: ${sourceEntry.name}`, ""];
|
|
4192
|
+
const enriched = capped.map((r) => {
|
|
4193
|
+
const isOutgoing = r.fromId === sourceInternalId;
|
|
4194
|
+
const otherId = isOutgoing ? r.toId : r.fromId;
|
|
4195
|
+
const other = otherEntries.get(otherId);
|
|
4196
|
+
const otherLabel = other?.entryId ? `${other.entryId}: ${other.name}` : other?.name ?? "(deleted)";
|
|
4197
|
+
const colSlug = other ? collMap.get(other.collectionId) ?? "unknown" : "unknown";
|
|
4198
|
+
return { isOutgoing, type: r.type, otherLabel, colSlug, otherEntryId: other?.entryId, otherName: other?.name ?? "(deleted)" };
|
|
4199
|
+
});
|
|
4200
|
+
const outgoing = enriched.filter((r) => r.isOutgoing);
|
|
4201
|
+
const incoming = enriched.filter((r) => !r.isOutgoing);
|
|
4202
|
+
if ((direction === "outgoing" || direction === "both") && outgoing.length > 0) {
|
|
4203
|
+
lines.push(`## Outgoing (${outgoing.length})`);
|
|
4204
|
+
for (const r of outgoing) {
|
|
4205
|
+
lines.push(`- \u2192 **${r.type}** ${r.otherLabel} [${r.colSlug}]`);
|
|
4206
|
+
}
|
|
4207
|
+
lines.push("");
|
|
4208
|
+
}
|
|
4209
|
+
if ((direction === "incoming" || direction === "both") && incoming.length > 0) {
|
|
4210
|
+
lines.push(`## Incoming (${incoming.length})`);
|
|
4211
|
+
for (const r of incoming) {
|
|
4212
|
+
lines.push(`- \u2190 **${r.type}** ${r.otherLabel} [${r.colSlug}]`);
|
|
4213
|
+
}
|
|
4214
|
+
lines.push("");
|
|
4215
|
+
}
|
|
4216
|
+
const shown = direction === "outgoing" ? outgoing : direction === "incoming" ? incoming : enriched;
|
|
4217
|
+
if (shown.length === 0) {
|
|
4218
|
+
lines.push(`No ${direction} relations found for \`${entryId}\`.`);
|
|
4219
|
+
}
|
|
4220
|
+
if (truncated) {
|
|
4221
|
+
lines.push(`_Showing first ${MAX_RELATIONS} of ${relations.length} relations. Use context action=get for the full picture._`);
|
|
4222
|
+
}
|
|
4223
|
+
const structuredRelations = shown.map((r) => ({
|
|
4224
|
+
entryId: r.otherEntryId,
|
|
4225
|
+
name: r.otherName,
|
|
4226
|
+
type: r.type,
|
|
4227
|
+
direction: r.isOutgoing ? "outgoing" : "incoming"
|
|
4228
|
+
}));
|
|
4229
|
+
const nodes = [
|
|
4230
|
+
{ id: entryId, name: sourceEntry.name, collectionName: "" },
|
|
4231
|
+
...shown.map((r) => ({
|
|
4232
|
+
id: r.otherEntryId ?? String(Math.random()),
|
|
4233
|
+
name: r.otherName,
|
|
4234
|
+
collectionName: r.colSlug
|
|
4235
|
+
}))
|
|
4236
|
+
];
|
|
4237
|
+
const edges = shown.filter((r) => r.otherEntryId).map((r) => ({
|
|
4238
|
+
source: entryId,
|
|
4239
|
+
target: r.otherEntryId,
|
|
4240
|
+
type: r.type
|
|
4241
|
+
}));
|
|
4242
|
+
return {
|
|
4243
|
+
content: [{ type: "text", text: lines.join("\n") }],
|
|
4244
|
+
structuredContent: success(
|
|
4245
|
+
`Found ${relations.length} relations for ${entryId}.`,
|
|
4246
|
+
{ entryId, relations: structuredRelations, nodes, edges, total: relations.length },
|
|
4247
|
+
[{ tool: "context", description: "View entry details", parameters: { action: "get", entryId } }]
|
|
4248
|
+
)
|
|
4249
|
+
};
|
|
4250
|
+
}
|
|
4251
|
+
|
|
4252
|
+
// src/tools/contextLookups.ts
|
|
4253
|
+
var LOOKUP_ACTIONS = ["get", "batch", "history", "list", "search", "find"];
|
|
4263
4254
|
var statusFilterSchema = z15.union([z15.string().max(200), z15.array(z15.enum(ENTRY_STATUS_READ_WORDS)).max(5)]);
|
|
4264
|
-
var LOOKUP_ACTION_DESCRIBE = "'get': one entry in full by ID. 'batch': up to 20 entries by ID. 'history': audit trail for an entry. 'list': browse entries with filters (accepted by default). 'search': full-text search across entries. ";
|
|
4265
|
-
var LOOKUP_TOOL_LINES = "- **get**: Fetch a single entry by ID \u2014 full record with data, labels, relations, history.\n- **batch**: Fetch multiple entries (max 20) in one call. Same shape as get per entry.\n- **history**: Audit trail for an entry.\n- **list**: Browse entries with optional filters (collection, status, tag, label). Use collections action=list first to discover slugs.\n- **search**: Full-text search across entries. Scope by collection or filter by status.\n";
|
|
4255
|
+
var LOOKUP_ACTION_DESCRIBE = "'get': one entry in full by ID. 'batch': up to 20 entries by ID. 'history': audit trail for an entry. 'list': browse entries with filters (accepted by default). 'search': full-text search across entries. 'find': an entry's direct relations (direction: incoming|outgoing|both, default both). ";
|
|
4256
|
+
var LOOKUP_TOOL_LINES = "- **get**: Fetch a single entry by ID \u2014 full record with data, labels, relations, history.\n- **batch**: Fetch multiple entries (max 20) in one call. Same shape as get per entry.\n- **history**: Audit trail for an entry.\n- **list**: Browse entries with optional filters (collection, status, tag, label). Use collections action=list first to discover slugs.\n- **search**: Full-text search across entries. Scope by collection or filter by status.\n- **find**: An entry's direct relations, incoming and outgoing. NOT full-text search (use search).\n";
|
|
4266
4257
|
var lookupParams = {
|
|
4267
4258
|
entryIds: z15.array(z15.string().max(200)).min(1).max(20).optional().describe("For 'batch': entry IDs, e.g. ['TYPE-strategy', 'STR-jljeg7']"),
|
|
4268
4259
|
collection: z15.string().max(200).optional().describe("For 'list'/'search': collection slug, e.g. 'glossary', 'tracking-events'."),
|
|
@@ -4291,6 +4282,11 @@ var lookupVariants = [
|
|
|
4291
4282
|
query: z15.string().min(2).max(500),
|
|
4292
4283
|
collection: z15.string().max(200).optional(),
|
|
4293
4284
|
status: statusFilterSchema.optional()
|
|
4285
|
+
}),
|
|
4286
|
+
z15.object({
|
|
4287
|
+
action: z15.literal("find"),
|
|
4288
|
+
entryId: z15.string().max(200),
|
|
4289
|
+
direction: z15.enum(["incoming", "outgoing", "both"]).optional().default("both")
|
|
4294
4290
|
})
|
|
4295
4291
|
];
|
|
4296
4292
|
var LOOKUP_ACTION_SPECS = {
|
|
@@ -4298,7 +4294,8 @@ var LOOKUP_ACTION_SPECS = {
|
|
|
4298
4294
|
batch: { params: ["entryIds"], description: "entryIds (1-20) is required." },
|
|
4299
4295
|
history: { params: ["entryId"], description: "entryId is required." },
|
|
4300
4296
|
list: { params: ["collection", "status", "tag", "label"], description: "Browse accepted entries by default; status='draft' lists drafts; status is one word. All params optional." },
|
|
4301
|
-
search: { params: ["query", "collection", "status"], description: "Full-text search; query is required (min 2 chars)." }
|
|
4297
|
+
search: { params: ["query", "collection", "status"], description: "Full-text search; query is required (min 2 chars)." },
|
|
4298
|
+
find: { params: ["entryId", "direction"], description: "entryId is required." }
|
|
4302
4299
|
};
|
|
4303
4300
|
function lookupHandlers(server) {
|
|
4304
4301
|
return {
|
|
@@ -4306,8 +4303,9 @@ function lookupHandlers(server) {
|
|
|
4306
4303
|
batch: (data) => handleBatch(data.entryIds),
|
|
4307
4304
|
history: (data) => handleGetHistory(data),
|
|
4308
4305
|
list: (data) => handleList(data.collection, data.status, data.tag, data.label),
|
|
4309
|
-
search: (data) => handleSearch(server, data.query, data.collection, data.status)
|
|
4306
|
+
search: (data) => handleSearch(server, data.query, data.collection, data.status),
|
|
4310
4307
|
// logs progress to the client
|
|
4308
|
+
find: (data) => handleFind(data.entryId, data.direction ?? "both")
|
|
4311
4309
|
};
|
|
4312
4310
|
}
|
|
4313
4311
|
|
|
@@ -4322,13 +4320,13 @@ function epistemicCollectionHint(collectionName) {
|
|
|
4322
4320
|
if (lc === "assumptions") return " \u2014 _check evidence strength with `context action=get`_";
|
|
4323
4321
|
return "";
|
|
4324
4322
|
}
|
|
4325
|
-
var CONTEXT_ACTIONS = [
|
|
4323
|
+
var CONTEXT_ACTIONS = [...LOOKUP_ACTIONS, "gather", "build", "neighborhood", "changes", "chain", "cross-cut", "incremental", "brief", "last-verified-brief"];
|
|
4326
4324
|
var contextSchema = z16.object({
|
|
4327
4325
|
// provenance: neighborhood BET-142; changes/chain/cross-cut/incremental/brief BET-239 (E4, E6)
|
|
4328
4326
|
action: z16.enum(CONTEXT_ACTIONS).describe(
|
|
4329
4327
|
LOOKUP_ACTION_DESCRIBE + "'gather': assemble knowledge context (entry graph, task auto-load, journey mode, or graph mode). 'build': structured build spec for an entry. 'neighborhood': typed graph neighborhood for an entry \u2014 blocking chain, dependencies, parent context, tensions, staleness. 'changes': entries modified and relations created since a timestamp. Requires 'since' parameter. 'chain': directed traversal along one relation type to depth 4. Requires entryId. Optional: direction, relationType, maxHops (1-4). 'cross-cut': structural aggregation \u2014 all relations of a given type grouped by source collection. Requires 'relationType' parameter. 'incremental': delta since last brief run for a skill. Requires 'skill' parameter. Returns only entries changed since the skill's last brief. 'brief': compound intelligence query. Requires 'briefType' parameter: 'steering' (changes + structure + delta + readiness), 'confidence' (changes + active bets \u2014 use the pb CLI's `tensions list` for tensions), or 'delta' (changes + relations since timestamp). Optional 'since' for delta type. 'last-verified-brief': fetch the most recent verified brief snapshot for a (templateId, scopeKey) pair (absorbs documents action=get-last-verified-brief). Requires templateId and scopeKey."
|
|
4330
4328
|
),
|
|
4331
|
-
entryId: z16.string().max(200).optional().describe("For 'get'/'history'/'build'/'neighborhood'/'chain': entry ID, e.g. '<PREFIX>-<n>'. For 'gather': optional entry
|
|
4329
|
+
entryId: z16.string().max(200).optional().describe("For 'get'/'history'/'find'/'build'/'neighborhood'/'chain': entry ID, e.g. '<PREFIX>-<n>'. For 'gather': optional (entry-graph mode)."),
|
|
4332
4330
|
...lookupParams,
|
|
4333
4331
|
mapEntryId: z16.string().max(200).optional().describe(
|
|
4334
4332
|
"For 'gather': journey map entry ID for journey-aware context. Returns context organised by journey stage. Takes precedence over entryId when both are supplied. Example: '<PREFIX>-<n>'."
|
|
@@ -4337,7 +4335,7 @@ var contextSchema = z16.object({
|
|
|
4337
4335
|
since: z16.string().max(200).optional().describe(
|
|
4338
4336
|
"For 'changes': ISO 8601 timestamp \u2014 returns entries/relations modified since this time. For 'brief' briefType='delta': optional custom timestamp. Example: '2026-03-24T00:00:00Z'."
|
|
4339
4337
|
),
|
|
4340
|
-
direction: z16.enum(["outgoing", "incoming"
|
|
4338
|
+
direction: z16.enum(["outgoing", "incoming", "both"]).optional().describe("For 'chain': 'outgoing' follows relations from source, 'incoming' to source (default outgoing). For 'find': incoming|outgoing|both (default both)."),
|
|
4341
4339
|
relationType: z16.string().max(200).optional().describe(
|
|
4342
4340
|
"Relation type filter. For 'chain': optional filter to traverse only this relation type. For 'cross-cut': required \u2014 scans all relations of this type across the workspace. Examples: 'part_of', 'informs', 'governs', 'depends_on'."
|
|
4343
4341
|
),
|
|
@@ -9347,7 +9345,7 @@ async function handleGetRun(runId, workflowId) {
|
|
|
9347
9345
|
run,
|
|
9348
9346
|
run.summary ? [
|
|
9349
9347
|
{
|
|
9350
|
-
tool: "
|
|
9348
|
+
tool: "context",
|
|
9351
9349
|
description: "See the summary entry's connections",
|
|
9352
9350
|
parameters: { action: "find", entryId: run.summary.entryId }
|
|
9353
9351
|
}
|
|
@@ -9552,7 +9550,7 @@ This checkpoint was NOT saved. The conversation context is preserved \u2014 cont
|
|
|
9552
9550
|
parameters: { action: "commit", entryId: result2.summary.entryId }
|
|
9553
9551
|
},
|
|
9554
9552
|
{
|
|
9555
|
-
tool: "
|
|
9553
|
+
tool: "context",
|
|
9556
9554
|
description: "See its connections",
|
|
9557
9555
|
parameters: { action: "find", entryId: result2.summary.entryId }
|
|
9558
9556
|
}
|
|
@@ -9728,7 +9726,7 @@ async function handleFacilitatedFinalization(wf, descriptor, roundId, normalized
|
|
|
9728
9726
|
},
|
|
9729
9727
|
[
|
|
9730
9728
|
{
|
|
9731
|
-
tool: "
|
|
9729
|
+
tool: "context",
|
|
9732
9730
|
description: "See its connections",
|
|
9733
9731
|
parameters: { action: "find", entryId: summaryEntryId }
|
|
9734
9732
|
}
|
|
@@ -12770,8 +12768,8 @@ var AGENT_CHEATSHEET = `# Product Brain \u2014 Agent Cheatsheet
|
|
|
12770
12768
|
| \`orient\` | Workspace context, governance, active work packages, session start | \`action\`: start, task |
|
|
12771
12769
|
| \`capture\` | Create entry | \`action\`: capture |
|
|
12772
12770
|
| \`entries\` | Write entries | \`action\`: update, commit, move, verify, preflight, still-true |
|
|
12773
|
-
| \`relations\` | Link entries, delete links
|
|
12774
|
-
| \`context\` | Reads; only incremental writes | \`action\`: get, batch, history, list, search, gather, build, neighborhood, changes, chain, cross-cut, incremental, brief, last-verified-brief |
|
|
12771
|
+
| \`relations\` | Link entries, delete links | \`action\`: create, batch-create, delete |
|
|
12772
|
+
| \`context\` | Reads; only incremental writes | \`action\`: get, batch, history, list, search, find, gather, build, neighborhood, changes, chain, cross-cut, incremental, brief, last-verified-brief |
|
|
12775
12773
|
| \`collections\` | Browse, describe, audit and export collections; manage labels | \`action\`: list, update, describe, audit, export, label-* |
|
|
12776
12774
|
| \`quality\` | Score an entry, verify the Chain, spine check | \`action\`: check, re-evaluate, verify-chain, spine-check |
|
|
12777
12775
|
| \`session\` | Start / close, wrapup, resume, constellation accept | \`action\`: start, close, status, wrapup-review, wrapup-commit, resume, commit-constellation |
|
|
@@ -12849,7 +12847,7 @@ Tags for filtering (e.g. \`severity:high\`), relations via \`entryRelations\`, v
|
|
|
12849
12847
|
'## Rules\nThe rules that bind you are in the briefing above, each with its why. A rule on no list binds nobody.\nOpen one: `context action=get entryId="<ID>"`. Find one: `context action=search query="<noun>"`.\nDraft a new rule: use the `draft-rule-from-context` prompt.'
|
|
12850
12848
|
);
|
|
12851
12849
|
sections.push(
|
|
12852
|
-
"## Knowledge Graph\nEntries are connected via typed relations (`entryRelations` table). Relations are bidirectional and collection-agnostic \u2014 any entry can link to any other entry.\n\n**Recommended relation types** (extensible \u2014 any string accepted):\n- `governs` \u2014 a rule constrains behavior of a feature\n- `defines_term_for` \u2014 a glossary term is canonical vocabulary for a feature/area\n- `belongs_to` \u2014 a feature belongs to a product area or parent concept (typed successor: `anchored_to`)\n- `informs` \u2014 a decision or insight informs a feature\n- `surfaces_tension_in` \u2014 a tension exists within a feature area\n- `related_to`, `depends_on`, `replaces`, `conflicts_with`, `references`, `confused_with`\n\nEach relation type is defined as a glossary entry (prefix `GT-REL-*`) to prevent terminology drift.\n\n**Tools:**\n- `context action=gather` \u2014 get the full context around any entry (multi-hop graph traversal)\n- `relations action=create` \u2014 create a typed link between two entries\n- `
|
|
12850
|
+
"## Knowledge Graph\nEntries are connected via typed relations (`entryRelations` table). Relations are bidirectional and collection-agnostic \u2014 any entry can link to any other entry.\n\n**Recommended relation types** (extensible \u2014 any string accepted):\n- `governs` \u2014 a rule constrains behavior of a feature\n- `defines_term_for` \u2014 a glossary term is canonical vocabulary for a feature/area\n- `belongs_to` \u2014 a feature belongs to a product area or parent concept (typed successor: `anchored_to`)\n- `informs` \u2014 a decision or insight informs a feature\n- `surfaces_tension_in` \u2014 a tension exists within a feature area\n- `related_to`, `depends_on`, `replaces`, `conflicts_with`, `references`, `confused_with`\n\nEach relation type is defined as a glossary entry (prefix `GT-REL-*`) to prevent terminology drift.\n\n**Tools:**\n- `context action=gather` \u2014 get the full context around any entry (multi-hop graph traversal)\n- `relations action=create` \u2014 create a typed link between two entries\n- `context action=find` \u2014 list direct relations for an entry\n\n**Convention:** A relation is written only when you ask for it, with exactly the verb you send \u2014 link the entries you know are related with `relations action=create`."
|
|
12853
12851
|
);
|
|
12854
12852
|
sections.push(
|
|
12855
12853
|
`## Creating Knowledge
|
|
@@ -13552,4 +13550,4 @@ export {
|
|
|
13552
13550
|
createProductBrainServer,
|
|
13553
13551
|
initFeatureFlags
|
|
13554
13552
|
};
|
|
13555
|
-
//# sourceMappingURL=chunk-
|
|
13553
|
+
//# sourceMappingURL=chunk-DT756ONZ.js.map
|