@productbrain/mcp 0.0.1-beta.8845 → 0.0.1-beta.8855
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/{chunk-LPG7TJRX.js → chunk-OEONSR7Q.js} +69 -323
- package/dist/chunk-OEONSR7Q.js.map +1 -0
- package/dist/{chunk-NT3NE6V5.js → chunk-VZNA24SX.js} +1 -3
- package/dist/chunk-VZNA24SX.js.map +1 -0
- package/dist/cli/index.js +1 -1
- package/dist/http.js +2 -2
- package/dist/index.js +2 -2
- package/dist/{setup-VUX6TU6Q.js → setup-X5BRRUTV.js} +4 -4
- package/dist/{setup-VUX6TU6Q.js.map → setup-X5BRRUTV.js.map} +1 -1
- package/package.json +1 -1
- package/dist/chunk-LPG7TJRX.js.map +0 -1
- package/dist/chunk-NT3NE6V5.js.map +0 -1
|
@@ -42,7 +42,7 @@ import {
|
|
|
42
42
|
trackSessionCaptureRate,
|
|
43
43
|
trackZeroCaptureAuditFired,
|
|
44
44
|
validateCallerSession
|
|
45
|
-
} from "./chunk-
|
|
45
|
+
} from "./chunk-VZNA24SX.js";
|
|
46
46
|
|
|
47
47
|
// src/server.ts
|
|
48
48
|
import { McpServer as McpServer2 } from "@modelcontextprotocol/sdk/server/mcp.js";
|
|
@@ -357,7 +357,6 @@ var TOOL_NAME_MIGRATIONS = /* @__PURE__ */ new Map([
|
|
|
357
357
|
["relate-entries", 'relations action="create"'],
|
|
358
358
|
["batch-relate", 'relations action="batch-create"'],
|
|
359
359
|
["find-related", 'relations action="find"'],
|
|
360
|
-
["suggest-links", 'relations action="suggest"'],
|
|
361
360
|
["gather-context", 'context action="gather"'],
|
|
362
361
|
["get-build-context", 'context action="build"'],
|
|
363
362
|
["list-collections", 'collections action="list"'],
|
|
@@ -405,7 +404,7 @@ var TOOL_NAME_MIGRATIONS = /* @__PURE__ */ new Map([
|
|
|
405
404
|
["move-entry", 'entries action="move"'],
|
|
406
405
|
["session-wrapup", 'session action="wrapup-review|wrapup-commit" (review \u2192 wrapup-review, commit-all \u2192 wrapup-commit)'],
|
|
407
406
|
["facilitate", 'session action="resume|commit-constellation" (same action names)'],
|
|
408
|
-
["graph", 'relations action="find
|
|
407
|
+
["graph", 'relations action="find" (same action name)'],
|
|
409
408
|
["documents", 'context action="last-verified-brief"'],
|
|
410
409
|
["labels", 'collections action="label-*" (list/create/update/delete/apply/remove \u2192 label-<same>)'],
|
|
411
410
|
["health", 'workspace action="check|whoami|status|audit|self-test" (same action names)'],
|
|
@@ -2302,7 +2301,7 @@ Use \`entries action=get\` to inspect the existing entry, or \`entries action=up
|
|
|
2302
2301
|
if (isBetOrGoal && !hasStrategyLink) {
|
|
2303
2302
|
lines.push("");
|
|
2304
2303
|
lines.push(
|
|
2305
|
-
`**Strategy link:** This ${isWorkPackageOrBetCapture ? "work package" : "goal"} doesn't connect to any strategy entry. Consider linking before accepting.
|
|
2304
|
+
`**Strategy link:** This ${isWorkPackageOrBetCapture ? "work package" : "goal"} doesn't connect to any strategy entry. Consider linking before accepting. Find one with \`entries action=search\` and link it with \`relations action=create\`.`
|
|
2306
2305
|
);
|
|
2307
2306
|
await recordSessionActivity({ strategyLinkWarnedForEntryId: internalId });
|
|
2308
2307
|
}
|
|
@@ -2334,13 +2333,13 @@ Use \`entries action=get\` to inspect the existing entry, or \`entries action=up
|
|
|
2334
2333
|
lines.push("## Next Steps");
|
|
2335
2334
|
const eid = finalEntryId || "(check entry ID)";
|
|
2336
2335
|
if (finalStatus === "committed") {
|
|
2337
|
-
lines.push(`1. **Connect it:** \`relations action=
|
|
2336
|
+
lines.push(`1. **Connect it:** find related entries with \`entries action=search\`, then \`relations action=create from="${eid}" to="<ID>" type="<verb>"\``);
|
|
2338
2337
|
if (hasQualityGaps) {
|
|
2339
2338
|
lines.push(`2. **Improve quality:** \`entries action=update entryId="${eid}"\` \u2014 fill missing fields`);
|
|
2340
2339
|
}
|
|
2341
2340
|
} else {
|
|
2342
2341
|
if (userLinkResults.length === 0) {
|
|
2343
|
-
lines.push(`1. **Connect it:** \`relations action=
|
|
2342
|
+
lines.push(`1. **Connect it:** find related entries with \`entries action=search\`, then \`relations action=create from="${eid}" to="<ID>" type="<verb>"\``);
|
|
2344
2343
|
}
|
|
2345
2344
|
lines.push(`${userLinkResults.length === 0 ? "2" : "1"}. **Review the possibility:** Keep this visible draft while it is an alternative. Accept only when commitment is intended and your role is authorized. Types reserved for human acceptance require human ratification. An audit advisory is not a reason to accept it.`);
|
|
2346
2345
|
if (hasQualityGaps) {
|
|
@@ -2363,11 +2362,8 @@ Use \`entries action=get\` to inspect the existing entry, or \`entries action=up
|
|
|
2363
2362
|
}
|
|
2364
2363
|
const next = [];
|
|
2365
2364
|
if (finalStatus === "committed") {
|
|
2366
|
-
next.push({ tool: "relations", description: "
|
|
2365
|
+
next.push({ tool: "relations", description: "See its connections", parameters: { action: "find", entryId: finalEntryId } });
|
|
2367
2366
|
} else {
|
|
2368
|
-
if (userLinkResults.length === 0) {
|
|
2369
|
-
next.push({ tool: "relations", description: "Discover links", parameters: { action: "suggest", entryId: finalEntryId } });
|
|
2370
|
-
}
|
|
2371
2367
|
next.push({ tool: "entries", description: "Review the draft alternative", parameters: { action: "get", entryId: finalEntryId } });
|
|
2372
2368
|
next.push({ tool: "entries", description: "Accept onto Chain", parameters: { action: "commit", entryId: finalEntryId } });
|
|
2373
2369
|
}
|
|
@@ -2925,7 +2921,7 @@ No DB writes \u2014 call without \`preview:true\` to accept for real.` }],
|
|
|
2925
2921
|
} catch {
|
|
2926
2922
|
}
|
|
2927
2923
|
const next = [
|
|
2928
|
-
{ tool: "relations", description: "
|
|
2924
|
+
{ tool: "relations", description: "See its connections", parameters: { action: "find", entryId } },
|
|
2929
2925
|
{ tool: "entries", description: "View accepted entry", parameters: { action: "get", entryId } }
|
|
2930
2926
|
];
|
|
2931
2927
|
const summary = `Accepted ${entryId} (${entry.name}) onto the Chain.`;
|
|
@@ -3832,7 +3828,7 @@ async function handleGet(entryId) {
|
|
|
3832
3828
|
`Found ${entryId}: ${String(e.name)} [${String(e.collectionSlug ?? "")}].`,
|
|
3833
3829
|
entryData,
|
|
3834
3830
|
[
|
|
3835
|
-
{ tool: "relations", description: "
|
|
3831
|
+
{ tool: "relations", description: "See its connections", parameters: { action: "find", entryId } },
|
|
3836
3832
|
{ tool: "context", description: "Gather related context", parameters: { action: "gather", entryId } }
|
|
3837
3833
|
]
|
|
3838
3834
|
)
|
|
@@ -4080,15 +4076,13 @@ import { z as z13 } from "zod/v3";
|
|
|
4080
4076
|
|
|
4081
4077
|
// src/tools/graph.ts
|
|
4082
4078
|
import { z as z12 } from "zod/v3";
|
|
4083
|
-
var GRAPH_ACTIONS = ["find"
|
|
4079
|
+
var GRAPH_ACTIONS = ["find"];
|
|
4084
4080
|
var graphSchema = z12.object({
|
|
4085
4081
|
action: z12.enum(GRAPH_ACTIONS).describe(
|
|
4086
|
-
"'find': traverse relations from an entry (graph walk).
|
|
4082
|
+
"'find': traverse relations from an entry (graph walk)."
|
|
4087
4083
|
),
|
|
4088
4084
|
entryId: z12.string().max(200).describe("Entry ID, e.g. '<PREFIX>-<n>'"),
|
|
4089
|
-
direction: z12.enum(["incoming", "outgoing", "both"]).default("both").optional().describe("For find: 'incoming' = what references this, 'outgoing' = what this references")
|
|
4090
|
-
limit: z12.number().min(1).max(20).default(10).optional().describe("For suggest: max suggestions to return"),
|
|
4091
|
-
depth: z12.number().min(1).max(3).default(2).optional().describe("For suggest: graph traversal depth")
|
|
4085
|
+
direction: z12.enum(["incoming", "outgoing", "both"]).default("both").optional().describe("For find: 'incoming' = what references this, 'outgoing' = what this references")
|
|
4092
4086
|
});
|
|
4093
4087
|
var graphFindOutputSchema = z12.object({
|
|
4094
4088
|
entryId: z12.string(),
|
|
@@ -4100,18 +4094,6 @@ var graphFindOutputSchema = z12.object({
|
|
|
4100
4094
|
})),
|
|
4101
4095
|
total: z12.number()
|
|
4102
4096
|
});
|
|
4103
|
-
var graphSuggestOutputSchema = z12.object({
|
|
4104
|
-
entryId: z12.string(),
|
|
4105
|
-
suggestions: z12.array(z12.object({
|
|
4106
|
-
targetEntryId: z12.string().optional(),
|
|
4107
|
-
targetName: z12.string(),
|
|
4108
|
-
relationType: z12.string(),
|
|
4109
|
-
direction: z12.string(),
|
|
4110
|
-
confidence: z12.number(),
|
|
4111
|
-
reason: z12.string()
|
|
4112
|
-
})),
|
|
4113
|
-
total: z12.number()
|
|
4114
|
-
});
|
|
4115
4097
|
async function handleFind(entryId, direction) {
|
|
4116
4098
|
const relations = await kernelQuery("chain.listEntryRelations", { entryId });
|
|
4117
4099
|
if (relations.length === 0) {
|
|
@@ -4121,8 +4103,7 @@ async function handleFind(entryId, direction) {
|
|
|
4121
4103
|
`No relations found for '${entryId}'.`,
|
|
4122
4104
|
{ entryId, relations: [], nodes: [], edges: [], total: 0 },
|
|
4123
4105
|
[
|
|
4124
|
-
{ tool: "relations", description: "Create a relation", parameters: { action: "create", from: entryId } }
|
|
4125
|
-
{ tool: "relations", description: "Get suggestions", parameters: { action: "suggest", entryId } }
|
|
4106
|
+
{ tool: "relations", description: "Create a relation", parameters: { action: "create", from: entryId } }
|
|
4126
4107
|
]
|
|
4127
4108
|
)
|
|
4128
4109
|
};
|
|
@@ -4210,97 +4191,9 @@ async function handleFind(entryId, direction) {
|
|
|
4210
4191
|
)
|
|
4211
4192
|
};
|
|
4212
4193
|
}
|
|
4213
|
-
async function handleSuggest(entryId, limit, depth) {
|
|
4214
|
-
const result2 = await kernelQuery("chain.graphSuggestLinks", {
|
|
4215
|
-
entryId,
|
|
4216
|
-
maxHops: depth,
|
|
4217
|
-
limit
|
|
4218
|
-
});
|
|
4219
|
-
if (!result2?.suggestions || result2.suggestions.length === 0) {
|
|
4220
|
-
return failureResult(
|
|
4221
|
-
`No suggestions found for '${entryId}' \u2014 it may already be well-connected, or no similar entries exist.`,
|
|
4222
|
-
"NOT_FOUND",
|
|
4223
|
-
`No suggestions found for '${entryId}'.`,
|
|
4224
|
-
"Entry may already be well-connected.",
|
|
4225
|
-
[{ tool: "relations", description: "Check existing relations", parameters: { action: "find", entryId } }]
|
|
4226
|
-
);
|
|
4227
|
-
}
|
|
4228
|
-
const resolved = result2.resolvedEntry;
|
|
4229
|
-
const resolvedLabel = resolved ? `\`${resolved.entryId ?? resolved.name}\` (${resolved.name})` : `\`${entryId}\``;
|
|
4230
|
-
const suggestions = result2.suggestions;
|
|
4231
|
-
const graphCount = suggestions.filter((s) => s.graphDistance > 0).length;
|
|
4232
|
-
const textCount = suggestions.filter((s) => s.graphDistance === -1).length;
|
|
4233
|
-
const sourceId = resolved?.entryId ?? entryId;
|
|
4234
|
-
const lines = [
|
|
4235
|
-
`## Suggest Result`,
|
|
4236
|
-
"",
|
|
4237
|
-
`# Link Suggestions for ${resolvedLabel}`,
|
|
4238
|
-
`_${suggestions.length} potential connections (${graphCount} via graph, ${textCount} via text similarity)._`,
|
|
4239
|
-
""
|
|
4240
|
-
];
|
|
4241
|
-
const top3 = suggestions.slice(0, 3);
|
|
4242
|
-
lines.push("## Quick Wins (top 3)");
|
|
4243
|
-
const batchArgs = [];
|
|
4244
|
-
for (const s of top3) {
|
|
4245
|
-
const tid = s.entryId ?? "(no ID)";
|
|
4246
|
-
const relType = s.recommendedRelationType || "related_to";
|
|
4247
|
-
lines.push(`- **${tid}**: ${s.name} [${s.collectionSlug}] \u2014 ${s.score}/100 \u2192 \`${relType}\``);
|
|
4248
|
-
if (s.entryId) batchArgs.push(`{from:"${sourceId}",to:"${tid}",type:"${relType}"}`);
|
|
4249
|
-
}
|
|
4250
|
-
lines.push("");
|
|
4251
|
-
if (batchArgs.length > 0) {
|
|
4252
|
-
lines.push(`**Link all 3:** \`relations action=batch-create relations=[${batchArgs.join(",")}]\``);
|
|
4253
|
-
lines.push("");
|
|
4254
|
-
}
|
|
4255
|
-
if (suggestions.length > 3) {
|
|
4256
|
-
lines.push("## All Suggestions");
|
|
4257
|
-
for (let i = 0; i < suggestions.length; i++) {
|
|
4258
|
-
const s = suggestions[i];
|
|
4259
|
-
const scoreBar = "\u2588".repeat(Math.round(s.score / 10)) + "\u2591".repeat(10 - Math.round(s.score / 10));
|
|
4260
|
-
const hopLabel = s.graphDistance > 0 ? `${s.graphDistance}-hop` : "text";
|
|
4261
|
-
const recType = s.recommendedRelationType !== "related_to" ? ` \u2192 \`${s.recommendedRelationType}\`` : "";
|
|
4262
|
-
lines.push(`${i + 1}. **${s.entryId ?? "(no ID)"}**: ${s.name} [${s.collectionSlug}] (${hopLabel})`);
|
|
4263
|
-
lines.push(` ${scoreBar} ${s.score}/100${recType}`);
|
|
4264
|
-
if (s.preview) lines.push(` ${s.preview}`);
|
|
4265
|
-
lines.push(` _${s.reasoning}_`);
|
|
4266
|
-
}
|
|
4267
|
-
}
|
|
4268
|
-
lines.push("");
|
|
4269
|
-
lines.push(`**To link one:** \`relations action=create from="${sourceId}" to="{target_id}" type="{type}"\``);
|
|
4270
|
-
lines.push(`**To dismiss:** \`relations action=dismiss from="${sourceId}" to="{target_id}" type="{type}"\``);
|
|
4271
|
-
const structuredSuggestions = suggestions.map((s) => ({
|
|
4272
|
-
targetEntryId: s.entryId,
|
|
4273
|
-
targetName: s.name,
|
|
4274
|
-
relationType: s.recommendedRelationType || "related_to",
|
|
4275
|
-
direction: "outgoing",
|
|
4276
|
-
confidence: s.score,
|
|
4277
|
-
reason: s.reasoning
|
|
4278
|
-
}));
|
|
4279
|
-
const nodes = [
|
|
4280
|
-
{ id: sourceId, name: resolved?.name ?? entryId, collectionName: "" },
|
|
4281
|
-
...suggestions.map((s) => ({
|
|
4282
|
-
id: s.entryId ?? String(Math.random()),
|
|
4283
|
-
name: s.name,
|
|
4284
|
-
collectionName: s.collectionSlug
|
|
4285
|
-
}))
|
|
4286
|
-
];
|
|
4287
|
-
const edges = suggestions.filter((s) => s.entryId).map((s) => ({
|
|
4288
|
-
source: sourceId,
|
|
4289
|
-
target: s.entryId,
|
|
4290
|
-
type: s.recommendedRelationType || "suggested"
|
|
4291
|
-
}));
|
|
4292
|
-
return {
|
|
4293
|
-
content: [{ type: "text", text: lines.join("\n") }],
|
|
4294
|
-
structuredContent: success(
|
|
4295
|
-
`Found ${suggestions.length} link suggestions for ${resolvedLabel}.`,
|
|
4296
|
-
{ entryId: sourceId, suggestions: structuredSuggestions, nodes, edges, total: suggestions.length },
|
|
4297
|
-
[{ tool: "relations", description: "Create links", parameters: { action: "batch-create" } }]
|
|
4298
|
-
)
|
|
4299
|
-
};
|
|
4300
|
-
}
|
|
4301
4194
|
|
|
4302
4195
|
// src/tools/relations.ts
|
|
4303
|
-
var RELATIONS_ACTIONS = ["create", "batch-create", "
|
|
4196
|
+
var RELATIONS_ACTIONS = ["create", "batch-create", "delete", "find"];
|
|
4304
4197
|
var relationItemSchema = z13.object({
|
|
4305
4198
|
from: z13.string().max(200),
|
|
4306
4199
|
to: z13.string().max(200),
|
|
@@ -4308,39 +4201,28 @@ var relationItemSchema = z13.object({
|
|
|
4308
4201
|
});
|
|
4309
4202
|
var relationsSchema = z13.object({
|
|
4310
4203
|
action: z13.enum(RELATIONS_ACTIONS).describe(
|
|
4311
|
-
"'create': link two entries. 'batch-create': create multiple relations (validates every item before writing any). '
|
|
4204
|
+
"'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)."
|
|
4312
4205
|
),
|
|
4313
|
-
from: z13.string().max(200).optional().describe("For 'create'/'
|
|
4314
|
-
to: z13.string().max(200).optional().describe("For 'create'/'
|
|
4315
|
-
type: z13.string().max(200).optional().describe("For 'create'/'
|
|
4316
|
-
score: z13.number().optional().describe("For 'dismiss': suggestion score from action=suggest."),
|
|
4206
|
+
from: z13.string().max(200).optional().describe("For 'create'/'delete': source entry ID."),
|
|
4207
|
+
to: z13.string().max(200).optional().describe("For 'create'/'delete': target entry ID."),
|
|
4208
|
+
type: z13.string().max(200).optional().describe("For 'create'/'delete': relation type."),
|
|
4317
4209
|
relations: z13.array(relationItemSchema).min(1).max(RELATION_BATCH_MAX).optional().describe("For 'batch-create': array of {from, to, type}."),
|
|
4318
4210
|
// WP-316 S3: Preview gate — dry-run mode for action=create.
|
|
4319
4211
|
preview: z13.boolean().optional().describe("For 'create': if true, validates the relation without writing. Returns what would happen. Default false."),
|
|
4320
|
-
entryId: z13.string().max(200).optional().describe("For 'find'
|
|
4321
|
-
direction: z13.enum(["incoming", "outgoing", "both"]).optional().describe("For 'find': 'incoming' = what references this, 'outgoing' = what this references. Default 'both'.")
|
|
4322
|
-
limit: z13.number().min(1).max(20).optional().describe("For 'suggest': max suggestions to return. Default 10."),
|
|
4323
|
-
depth: z13.number().min(1).max(3).optional().describe("For 'suggest': graph traversal depth. Default 2.")
|
|
4212
|
+
entryId: z13.string().max(200).optional().describe("For 'find': entry ID, e.g. '<PREFIX>-<n>'."),
|
|
4213
|
+
direction: z13.enum(["incoming", "outgoing", "both"]).optional().describe("For 'find': 'incoming' = what references this, 'outgoing' = what this references. Default 'both'.")
|
|
4324
4214
|
});
|
|
4325
4215
|
var relationsCreateVariant = z13.object({
|
|
4326
4216
|
action: z13.literal("create"),
|
|
4327
4217
|
from: z13.string().max(200),
|
|
4328
4218
|
to: z13.string().max(200),
|
|
4329
4219
|
type: z13.string().max(200),
|
|
4330
|
-
score: z13.number().optional(),
|
|
4331
4220
|
preview: z13.boolean().optional()
|
|
4332
4221
|
});
|
|
4333
4222
|
var relationsBatchCreateVariant = z13.object({
|
|
4334
4223
|
action: z13.literal("batch-create"),
|
|
4335
4224
|
relations: z13.array(relationItemSchema).min(1).max(RELATION_BATCH_MAX)
|
|
4336
4225
|
});
|
|
4337
|
-
var relationsDismissVariant = z13.object({
|
|
4338
|
-
action: z13.literal("dismiss"),
|
|
4339
|
-
from: z13.string().max(200),
|
|
4340
|
-
to: z13.string().max(200),
|
|
4341
|
-
type: z13.string().max(200).optional(),
|
|
4342
|
-
score: z13.number().optional()
|
|
4343
|
-
});
|
|
4344
4226
|
var relationsDeleteVariant = z13.object({
|
|
4345
4227
|
action: z13.literal("delete"),
|
|
4346
4228
|
from: z13.string().max(200),
|
|
@@ -4352,44 +4234,32 @@ var relationsFindVariant = z13.object({
|
|
|
4352
4234
|
entryId: z13.string().max(200),
|
|
4353
4235
|
direction: z13.enum(["incoming", "outgoing", "both"]).optional().default("both")
|
|
4354
4236
|
});
|
|
4355
|
-
var relationsSuggestVariant = z13.object({
|
|
4356
|
-
action: z13.literal("suggest"),
|
|
4357
|
-
entryId: z13.string().max(200),
|
|
4358
|
-
limit: z13.number().min(1).max(20).optional().default(10),
|
|
4359
|
-
depth: z13.number().min(1).max(3).optional().default(2)
|
|
4360
|
-
});
|
|
4361
4237
|
var relationsActionUnion = z13.discriminatedUnion("action", [
|
|
4362
4238
|
relationsCreateVariant,
|
|
4363
4239
|
relationsBatchCreateVariant,
|
|
4364
|
-
relationsDismissVariant,
|
|
4365
4240
|
relationsDeleteVariant,
|
|
4366
|
-
relationsFindVariant
|
|
4367
|
-
relationsSuggestVariant
|
|
4241
|
+
relationsFindVariant
|
|
4368
4242
|
]);
|
|
4369
4243
|
var RELATIONS_ACTION_SPECS = {
|
|
4370
|
-
create: { params: ["from", "to", "type", "
|
|
4244
|
+
create: { params: ["from", "to", "type", "preview"], description: "from, to, and type are required." },
|
|
4371
4245
|
"batch-create": { params: ["relations"], description: "relations (1-20 items) is required." },
|
|
4372
|
-
dismiss: { params: ["from", "to", "type", "score"], description: "from and to are required." },
|
|
4373
4246
|
delete: { params: ["from", "to", "type"], description: "from, to, and type are required." },
|
|
4374
|
-
find: { params: ["entryId", "direction"], description: "entryId is required." }
|
|
4375
|
-
suggest: { params: ["entryId", "limit", "depth"], description: "entryId is required." }
|
|
4247
|
+
find: { params: ["entryId", "direction"], description: "entryId is required." }
|
|
4376
4248
|
};
|
|
4377
4249
|
function registerRelationsTools(server) {
|
|
4378
4250
|
const relationsHandlers = {
|
|
4379
|
-
create: (data) => handleCreate(data.from, data.to, data.type, data.
|
|
4251
|
+
create: (data) => handleCreate(data.from, data.to, data.type, data.preview),
|
|
4380
4252
|
"batch-create": (data) => handleBatchCreate(data.relations),
|
|
4381
|
-
dismiss: (data) => handleDismiss(data.from, data.to, data.type ?? "related_to", data.score),
|
|
4382
4253
|
delete: (data) => handleDelete(data.from, data.to, data.type),
|
|
4383
|
-
find: (data) => handleFind(data.entryId, data.direction ?? "both")
|
|
4384
|
-
suggest: (data) => handleSuggest(data.entryId, data.limit ?? 10, data.depth ?? 2)
|
|
4254
|
+
find: (data) => handleFind(data.entryId, data.direction ?? "both")
|
|
4385
4255
|
};
|
|
4386
4256
|
server.registerTool(
|
|
4387
4257
|
"relations",
|
|
4388
4258
|
{
|
|
4389
4259
|
title: "Relations",
|
|
4390
|
-
description: "Create, manage, and
|
|
4260
|
+
description: "Create, manage, and read relations between entries. A relation is written only when you ask for it, with exactly the verb you send.\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.\n- **find**: Traverse relations from a specific entry \u2014 shows incoming/outgoing connections. NOT full-text search (use entries action=search).",
|
|
4391
4261
|
inputSchema: relationsSchema,
|
|
4392
|
-
// Mixed read/write noun (§3 R3): find
|
|
4262
|
+
// Mixed read/write noun (§3 R3): find is read-only, create/batch-create/delete write.
|
|
4393
4263
|
annotations: { readOnlyHint: false, destructiveHint: true, openWorldHint: false }
|
|
4394
4264
|
},
|
|
4395
4265
|
thinWrapper(async (args) => {
|
|
@@ -4403,16 +4273,14 @@ function registerRelationsTools(server) {
|
|
|
4403
4273
|
})
|
|
4404
4274
|
);
|
|
4405
4275
|
}
|
|
4406
|
-
async function handleCreate(from, to, type,
|
|
4276
|
+
async function handleCreate(from, to, type, preview) {
|
|
4407
4277
|
requireWriteAccess();
|
|
4408
4278
|
const agentSessionId = getAgentSessionId();
|
|
4409
4279
|
const result2 = await kernelMutation("chain.createEntryRelation", {
|
|
4410
4280
|
fromEntryId: from,
|
|
4411
4281
|
toEntryId: to,
|
|
4412
4282
|
type,
|
|
4413
|
-
|
|
4414
|
-
// WP-474 E4-REWIRE: explicit relate = human/agent-CHOSEN type → mark "user"
|
|
4415
|
-
// (DEC-1399: out of gate scope, but positively marked so the judge never keys off absence).
|
|
4283
|
+
// Explicit relate = the caller chose the verb; DEC-1915 / TASK-601: it is written exactly as sent.
|
|
4416
4284
|
proposedBy: "user",
|
|
4417
4285
|
sessionId: agentSessionId ?? void 0,
|
|
4418
4286
|
...preview ? { preview: true } : {}
|
|
@@ -4478,16 +4346,13 @@ ${result2.blockingReason ?? "This relation would be rejected."}` }],
|
|
|
4478
4346
|
)
|
|
4479
4347
|
};
|
|
4480
4348
|
}
|
|
4481
|
-
const suggested2 = result2.suggestedType;
|
|
4482
4349
|
const advisoryText = (result2.advisories ?? []).map((a) => `
|
|
4483
4350
|
|
|
4484
4351
|
\u26A0 **${a.ruleId}** (org stage: ${a.stage}): ${a.ruleText}`).join("");
|
|
4485
4352
|
return {
|
|
4486
4353
|
content: [{ type: "text", text: `# Preview: would relate ${from} \u2192 ${to} (${type})
|
|
4487
4354
|
|
|
4488
|
-
No DB writes \u2014 call without \`preview:true\` to create for real.${
|
|
4489
|
-
|
|
4490
|
-
**Type suggestion:** \`${suggested2.type}\` may be more precise (${suggested2.confidence}% confidence).` : ""}${advisoryText}` }],
|
|
4355
|
+
No DB writes \u2014 call without \`preview:true\` to create for real.${advisoryText}` }],
|
|
4491
4356
|
structuredContent: success(
|
|
4492
4357
|
`Preview: would relate ${from} \u2192 ${to} (${type}) \u2014 no DB writes`,
|
|
4493
4358
|
{
|
|
@@ -4495,7 +4360,6 @@ No DB writes \u2014 call without \`preview:true\` to create for real.${suggested
|
|
|
4495
4360
|
from,
|
|
4496
4361
|
to,
|
|
4497
4362
|
type,
|
|
4498
|
-
...suggested2 && { suggestedType: suggested2 },
|
|
4499
4363
|
...result2.advisories && result2.advisories.length > 0 && { advisories: result2.advisories }
|
|
4500
4364
|
},
|
|
4501
4365
|
[{ tool: "relations", description: "Create for real", parameters: { action: "create", from, to, type } }]
|
|
@@ -4509,13 +4373,6 @@ No DB writes \u2014 call without \`preview:true\` to create for real.${suggested
|
|
|
4509
4373
|
`**${from}** \u2014[${type}]\u2192 **${to}**`,
|
|
4510
4374
|
`**Workspace:** ${wsCtx.workspaceSlug} (${wsCtx.workspaceId})`
|
|
4511
4375
|
];
|
|
4512
|
-
const suggested = result2?.suggestedType;
|
|
4513
|
-
if (suggested) {
|
|
4514
|
-
lines.push("");
|
|
4515
|
-
lines.push(`**Type suggestion:** \`${suggested.type}\` may be more precise (${suggested.confidence}% confidence).`);
|
|
4516
|
-
lines.push(`_${suggested.reasoning}_`);
|
|
4517
|
-
lines.push(`To use instead: \`relations action=create from="${from}" to="${to}" type="${suggested.type}"\``);
|
|
4518
|
-
}
|
|
4519
4376
|
for (const a of result2?.advisories ?? []) {
|
|
4520
4377
|
lines.push("");
|
|
4521
4378
|
lines.push(`\u26A0 Written anyway (org stage: ${a.stage}): ${a.ruleText}`);
|
|
@@ -4529,7 +4386,6 @@ No DB writes \u2014 call without \`preview:true\` to create for real.${suggested
|
|
|
4529
4386
|
from,
|
|
4530
4387
|
to,
|
|
4531
4388
|
type,
|
|
4532
|
-
...suggested && { suggestedType: suggested },
|
|
4533
4389
|
...result2?.advisories && result2.advisories.length > 0 && { advisories: result2.advisories }
|
|
4534
4390
|
},
|
|
4535
4391
|
[{ tool: "relations", description: "See connections", parameters: { action: "find", entryId: from } }]
|
|
@@ -4587,28 +4443,6 @@ async function handleBatchCreate(relations) {
|
|
|
4587
4443
|
)
|
|
4588
4444
|
};
|
|
4589
4445
|
}
|
|
4590
|
-
async function handleDismiss(from, to, type, score) {
|
|
4591
|
-
requireWriteAccess();
|
|
4592
|
-
const fromEntry = await kernelQuery("chain.getEntry", { entryId: from });
|
|
4593
|
-
if (!fromEntry) {
|
|
4594
|
-
return notFoundResult(from);
|
|
4595
|
-
}
|
|
4596
|
-
const toEntry = await kernelQuery("chain.getEntry", { entryId: to });
|
|
4597
|
-
if (!toEntry) {
|
|
4598
|
-
return notFoundResult(to);
|
|
4599
|
-
}
|
|
4600
|
-
await kernelMutation("chain.dismissSuggestion", {
|
|
4601
|
-
fromEntryId: fromEntry._id,
|
|
4602
|
-
suggestedEntryId: toEntry._id,
|
|
4603
|
-
recommendedType: type,
|
|
4604
|
-
score: score ?? 0
|
|
4605
|
-
});
|
|
4606
|
-
return successResult(
|
|
4607
|
-
`Dismissed suggestion: ${from} \u2192 ${to} (${type}). Feedback recorded for scoring improvements.`,
|
|
4608
|
-
`Dismissed suggestion ${from} \u2192 ${to} (${type}). Feedback recorded.`,
|
|
4609
|
-
{ from, to, type }
|
|
4610
|
-
);
|
|
4611
|
-
}
|
|
4612
4446
|
async function handleDelete(from, to, type) {
|
|
4613
4447
|
requireWriteAccess();
|
|
4614
4448
|
await kernelMutation("chain.removeEntryRelation", {
|
|
@@ -4892,12 +4726,12 @@ async function handleGather(server, entryId, task, mode, maxHops, maxResults, st
|
|
|
4892
4726
|
|
|
4893
4727
|
_No relations found._ This entry is not yet connected to the knowledge graph.
|
|
4894
4728
|
|
|
4895
|
-
Use \`relations action=
|
|
4729
|
+
Use \`relations action=create\` to link it to the entries it relates to.`
|
|
4896
4730
|
}],
|
|
4897
4731
|
structuredContent: success(
|
|
4898
4732
|
`No relations found for ${root2.entryId}. Entry not yet connected.`,
|
|
4899
4733
|
{ root: root2, context: [], totalFound: 0 },
|
|
4900
|
-
[{ tool: "relations", description: "
|
|
4734
|
+
[{ tool: "relations", description: "Link it", parameters: { action: "create", from: root2.entryId } }]
|
|
4901
4735
|
)
|
|
4902
4736
|
};
|
|
4903
4737
|
}
|
|
@@ -5124,12 +4958,12 @@ _This helps future sessions start with better constraints and context._`
|
|
|
5124
4958
|
|
|
5125
4959
|
_No relations found._ This entry is not yet connected to the knowledge graph.
|
|
5126
4960
|
|
|
5127
|
-
Use \`relations action=
|
|
4961
|
+
Use \`relations action=create\` to link it.`
|
|
5128
4962
|
}],
|
|
5129
4963
|
structuredContent: success(
|
|
5130
4964
|
`No relations found for ${root.entryId}. Entry not yet connected.`,
|
|
5131
4965
|
{ root, related: [], totalRelations: 0 },
|
|
5132
|
-
[{ tool: "relations", description: "
|
|
4966
|
+
[{ tool: "relations", description: "Link it", parameters: { action: "create", from: root.entryId } }]
|
|
5133
4967
|
)
|
|
5134
4968
|
};
|
|
5135
4969
|
}
|
|
@@ -5312,14 +5146,14 @@ async function handleNeighborhood(entryId) {
|
|
|
5312
5146
|
const isEmpty = result2.blockingChain.length === 0 && result2.dependencyChain.length === 0 && !result2.parentContext && result2.relatedTensions.length === 0;
|
|
5313
5147
|
if (isEmpty) {
|
|
5314
5148
|
lines.push(`_No neighborhood data found. Entry may not exist or has no graph connections._`);
|
|
5315
|
-
lines.push(`Use \`relations action=
|
|
5149
|
+
lines.push(`Use \`relations action=create\` to link it to the entries it relates to.`);
|
|
5316
5150
|
}
|
|
5317
5151
|
return {
|
|
5318
5152
|
content: [{ type: "text", text: lines.join("\n") }],
|
|
5319
5153
|
structuredContent: success(
|
|
5320
5154
|
`Neighborhood for ${entryId}: ${result2.blockingChain.length} blocking, ${result2.dependencyChain.length} dependencies, ${result2.relatedTensions.length} tensions.`,
|
|
5321
5155
|
result2,
|
|
5322
|
-
isEmpty ? [{ tool: "relations", description: "
|
|
5156
|
+
isEmpty ? [{ tool: "relations", description: "Link it", parameters: { action: "create", from: entryId } }] : void 0
|
|
5323
5157
|
)
|
|
5324
5158
|
};
|
|
5325
5159
|
}
|
|
@@ -7289,11 +7123,11 @@ function buildOrientBriefingLines(view, opts) {
|
|
|
7289
7123
|
lines.splice(pull < 0 ? lines.length : pull, 0, RULE_LISTS_EVERYONE_ONLY_LABEL, "(none currently served)", "");
|
|
7290
7124
|
return lines;
|
|
7291
7125
|
}
|
|
7292
|
-
async function fetchAndRenderOpenerDirection(
|
|
7126
|
+
async function fetchAndRenderOpenerDirection(kernelQuery3, errors, session) {
|
|
7293
7127
|
let view = null;
|
|
7294
7128
|
let fetchFailed = false;
|
|
7295
7129
|
try {
|
|
7296
|
-
view = await
|
|
7130
|
+
view = await kernelQuery3("chain.getOrientView", {
|
|
7297
7131
|
tier: "standard",
|
|
7298
7132
|
invocationPath: session?.invocationPath ?? "session-start",
|
|
7299
7133
|
// Stamps this call onto the current session/conversation/task (same as every other
|
|
@@ -7308,9 +7142,9 @@ async function fetchAndRenderOpenerDirection(kernelQuery2, errors, session) {
|
|
|
7308
7142
|
}
|
|
7309
7143
|
return { lines: [...buildOrientBriefingLines(view), ...buildRitualsSurfaceLines(view, { fetchFailed: fetchFailed || view?.fetchFailed === true })], view };
|
|
7310
7144
|
}
|
|
7311
|
-
async function buildAlreadyOrientedResult(
|
|
7145
|
+
async function buildAlreadyOrientedResult(kernelQuery3, agentSessionId, taskGroundingWarningLines2) {
|
|
7312
7146
|
const errors = [];
|
|
7313
|
-
const { lines, view } = await fetchAndRenderOpenerDirection(
|
|
7147
|
+
const { lines, view } = await fetchAndRenderOpenerDirection(kernelQuery3, errors, { agentSessionId, invocationPath: "manual-orient" });
|
|
7314
7148
|
return {
|
|
7315
7149
|
content: [{
|
|
7316
7150
|
type: "text",
|
|
@@ -8979,7 +8813,7 @@ Use \`workflows action=checkpoint\` after each round to persist progress to the
|
|
|
8979
8813
|
label: "Capture & Accept",
|
|
8980
8814
|
type: "close",
|
|
8981
8815
|
instruction: "Capture the pitch to the Chain. Accept draft entries. Connect the constellation.",
|
|
8982
|
-
facilitatorGuidance: "List all draft entries created during shaping (elements, risks, decisions, glossary terms). Ask the user to confirm: 'Ready to accept these onto the Chain?' Call `session action=commit-constellation` to accept the work package and all linked entries \u2014 sequentially, best-effort per entry; check failedIds and conflictIds. Then
|
|
8816
|
+
facilitatorGuidance: "List all draft entries created during shaping (elements, risks, decisions, glossary terms). Ask the user to confirm: 'Ready to accept these onto the Chain?' Call `session action=commit-constellation` to accept the work package and all linked entries \u2014 sequentially, best-effort per entry; check failedIds and conflictIds. Then link the work package to the entries it relates to via `relations action=batch-create`. Thank the user. State: what was shaped, how many constellation entries captured, what the implementer inherits.",
|
|
8983
8817
|
outputSchema: {
|
|
8984
8818
|
field: "capture",
|
|
8985
8819
|
description: "Final capture summary \u2014 entries accepted, relations created",
|
|
@@ -9171,7 +9005,7 @@ This block is the final deliverable of the review. Everything else is supporting
|
|
|
9171
9005
|
label: "Capture & Close",
|
|
9172
9006
|
type: "close",
|
|
9173
9007
|
instruction: "Finalize the review as an insight draft. Connect new entries from learning extraction. Use session action=wrapup-review. Thank the participant.",
|
|
9174
|
-
facilitatorGuidance: "Summarize the review for finalization. Complete the terminal round through `workflows action=checkpoint` with `isFinal=true` so the workflow substrate creates the draft insight record. Include: BET/feature IDs, verdict, key findings, fixes applied, and the structured verdict block from Round 05. The insight description MUST include the 5-item verdict block (verdict Y/N, what shipped, why it matters, user benefit, how to validate). If learnings were captured in Round 04:
|
|
9008
|
+
facilitatorGuidance: "Summarize the review for finalization. Complete the terminal round through `workflows action=checkpoint` with `isFinal=true` so the workflow substrate creates the draft insight record. Include: BET/feature IDs, verdict, key findings, fixes applied, and the structured verdict block from Round 05. The insight description MUST include the 5-item verdict block (verdict Y/N, what shipped, why it matters, user benefit, how to validate). If learnings were captured in Round 04: link each new entry to what it relates to with relations action=batch-create. Run session action=wrapup-review before closing. Thank the participant. State what was fixed, what was captured, what was deferred.",
|
|
9175
9009
|
outputSchema: {
|
|
9176
9010
|
field: "capture",
|
|
9177
9011
|
description: "Insight entry created, WP IDs referenced, learnings connected",
|
|
@@ -9236,7 +9070,6 @@ var PROCESS_CHANGE_WORKFLOW_DESCRIPTOR = {
|
|
|
9236
9070
|
- \`orient\` \u2014 load workspace context (governance, active work packages, stale entries)
|
|
9237
9071
|
- \`entries action=search\` \u2014 find related entries
|
|
9238
9072
|
- \`capture\` \u2014 manual fallback only if workflow finalization cannot safely route the entry
|
|
9239
|
-
- \`relations action=suggest\` \u2014 discover connections
|
|
9240
9073
|
- \`relations action=create\` or \`relations action=batch-create\` \u2014 link to related entries
|
|
9241
9074
|
- \`entries action=commit\` \u2014 promote to SSOT (only on user confirmation)
|
|
9242
9075
|
- \`workflows action=checkpoint\` \u2014 save progress if needed
|
|
@@ -9296,7 +9129,7 @@ var PROCESS_CHANGE_WORKFLOW_DESCRIPTOR = {
|
|
|
9296
9129
|
label: "Capture & Close",
|
|
9297
9130
|
type: "close",
|
|
9298
9131
|
instruction: "Capture to the Chain. Link to related entries. Confirm with the user.",
|
|
9299
|
-
facilitatorGuidance: "Finalize through workflows action=checkpoint with isFinal=true, passing a summaryName when you have a clean title. The workflow substrate will create the primary record as a draft and attach it to the durable run. After finalization,
|
|
9132
|
+
facilitatorGuidance: "Finalize through workflows action=checkpoint with isFinal=true, passing a summaryName when you have a clean title. The workflow substrate will create the primary record as a draft and attach it to the durable run. After finalization, link the entry to the 2-3 entries it relates to with relations action=create. Show the user what was captured: entry ID, collection it landed in, relations created. Ask if they want to accept now or leave as draft. If they confirm, use entries action=commit. If there are stale entries to reconfirm, mention them. Thank the user briefly and exit \u2014 don't linger.",
|
|
9300
9133
|
outputSchema: {
|
|
9301
9134
|
field: "capture",
|
|
9302
9135
|
description: "Entry captured, relations created, accept status",
|
|
@@ -9746,8 +9579,8 @@ async function handleGetRun(runId, workflowId) {
|
|
|
9746
9579
|
run.summary ? [
|
|
9747
9580
|
{
|
|
9748
9581
|
tool: "relations",
|
|
9749
|
-
description: "
|
|
9750
|
-
parameters: { action: "
|
|
9582
|
+
description: "See the summary entry's connections",
|
|
9583
|
+
parameters: { action: "find", entryId: run.summary.entryId }
|
|
9751
9584
|
}
|
|
9752
9585
|
] : void 0
|
|
9753
9586
|
)
|
|
@@ -9928,7 +9761,7 @@ This checkpoint was NOT saved. The conversation context is preserved \u2014 cont
|
|
|
9928
9761
|
`Name: ${result2.summary.name}`,
|
|
9929
9762
|
"",
|
|
9930
9763
|
`The summary is captured as a draft. Use \`entries action=commit entryId="${result2.summary.entryId}"\` to promote it to the Chain.`,
|
|
9931
|
-
`Use \`relations action=
|
|
9764
|
+
`Use \`relations action=create\` to link it to the entries it relates to.`
|
|
9932
9765
|
);
|
|
9933
9766
|
return {
|
|
9934
9767
|
content: [{ type: "text", text: lines.join("\n") }],
|
|
@@ -9951,8 +9784,8 @@ This checkpoint was NOT saved. The conversation context is preserved \u2014 cont
|
|
|
9951
9784
|
},
|
|
9952
9785
|
{
|
|
9953
9786
|
tool: "relations",
|
|
9954
|
-
description: "
|
|
9955
|
-
parameters: { action: "
|
|
9787
|
+
description: "See its connections",
|
|
9788
|
+
parameters: { action: "find", entryId: result2.summary.entryId }
|
|
9956
9789
|
}
|
|
9957
9790
|
]
|
|
9958
9791
|
)
|
|
@@ -10127,8 +9960,8 @@ async function handleFacilitatedFinalization(wf, descriptor, roundId, normalized
|
|
|
10127
9960
|
[
|
|
10128
9961
|
{
|
|
10129
9962
|
tool: "relations",
|
|
10130
|
-
description: "
|
|
10131
|
-
parameters: { action: "
|
|
9963
|
+
description: "See its connections",
|
|
9964
|
+
parameters: { action: "find", entryId: summaryEntryId }
|
|
10132
9965
|
}
|
|
10133
9966
|
]
|
|
10134
9967
|
)
|
|
@@ -10433,7 +10266,7 @@ function registerQualityTools(server) {
|
|
|
10433
10266
|
"quality",
|
|
10434
10267
|
{
|
|
10435
10268
|
title: "Quality",
|
|
10436
|
-
description: "Assess, re-evaluate, and verify entry/work package quality. Four actions:\n\n- **check**: Read the entry's server quality verdict (tier + criteria) \u2014 it does not re-score
|
|
10269
|
+
description: "Assess, re-evaluate, and verify entry/work package quality. Four actions:\n\n- **check**: Read the entry's server quality verdict (tier + criteria) \u2014 it does not re-score.\n- **re-evaluate**: Trigger a fresh quality evaluation. Returns heuristic verdict immediately, schedules LLM evaluation in background.\n- **verify-chain**: Verify knowledge entries against the actual codebase (codeMapping drift, cross-references) \u2014 absorbs `verify`. Produces a trust report with a trust score.\n- **spine-check**: Structural completeness of the purpose+vision+strategy spine \u2014 duplicate singletons, connectedness, per-strategy refusal heuristic; story:true also runs the on-demand one-story LLM judgment (never scheduled, result labelled [unvalidated]). Async: a plain call reads back the latest stored verdict and auto-schedules a new run ONLY when none is already in flight AND the stored one is absent/stale/missing the requested story pass \u2014 repeated polling never re-triggers work. Pass refresh:true to force a fresh run regardless.",
|
|
10437
10270
|
inputSchema: qualitySchema,
|
|
10438
10271
|
annotations: { readOnlyHint: false, destructiveHint: false, idempotentHint: true, openWorldHint: false }
|
|
10439
10272
|
},
|
|
@@ -10448,30 +10281,6 @@ function registerQualityTools(server) {
|
|
|
10448
10281
|
})
|
|
10449
10282
|
);
|
|
10450
10283
|
}
|
|
10451
|
-
async function appendLinkSuggestions(entryId, lines) {
|
|
10452
|
-
let relationCount = -1;
|
|
10453
|
-
try {
|
|
10454
|
-
const relations = await kernelQuery("chain.listEntryRelations", { entryId });
|
|
10455
|
-
relationCount = relations?.length ?? 0;
|
|
10456
|
-
} catch {
|
|
10457
|
-
return;
|
|
10458
|
-
}
|
|
10459
|
-
if (relationCount !== 0) return;
|
|
10460
|
-
try {
|
|
10461
|
-
const suggestions = await kernelQuery("chain.graphSuggestLinks", {
|
|
10462
|
-
entryId,
|
|
10463
|
-
maxHops: 2,
|
|
10464
|
-
limit: 3
|
|
10465
|
-
});
|
|
10466
|
-
if ((suggestions?.suggestions?.length ?? 0) > 0) {
|
|
10467
|
-
const linkHints = (suggestions.suggestions ?? []).map((s) => ` \u2192 \`relations action=create from="${entryId}" to="${s.entryId}" type="${s.recommendedRelationType}"\` \u2014 ${s.name} [${s.collectionSlug}] (${s.score}/100)`).join("\n");
|
|
10468
|
-
lines.push(`
|
|
10469
|
-
## Suggested Links to Improve Quality
|
|
10470
|
-
${linkHints}`);
|
|
10471
|
-
}
|
|
10472
|
-
} catch {
|
|
10473
|
-
}
|
|
10474
|
-
}
|
|
10475
10284
|
async function handleCheck(entryId) {
|
|
10476
10285
|
const res = await kernelQuery("quality.getLatestVerdictForEntry", { entryId });
|
|
10477
10286
|
if (!res || !res.entryFound) {
|
|
@@ -10491,7 +10300,6 @@ Entry \`${entryId}\` not found. Use \`search\` to find the right ID.`,
|
|
|
10491
10300
|
"",
|
|
10492
10301
|
`No quality verdict yet for \`${entryId}\`. Run \`quality action=re-evaluate entryId="${entryId}"\` to evaluate it.`
|
|
10493
10302
|
];
|
|
10494
|
-
await appendLinkSuggestions(entryId, lines2);
|
|
10495
10303
|
return successResult(
|
|
10496
10304
|
lines2.join("\n"),
|
|
10497
10305
|
`No quality verdict yet for ${entryId} \u2014 re-evaluate to produce one.`,
|
|
@@ -10512,7 +10320,6 @@ Entry \`${entryId}\` not found. Use \`search\` to find the right ID.`,
|
|
|
10512
10320
|
const verdictSection = formatRubricVerdictSection(verdict);
|
|
10513
10321
|
lines.push(verdictSection || `Verdict tier \`${verdict.tier ?? "unknown"}\` for \`${entryId}\` \u2014 no rubric criteria to report.`);
|
|
10514
10322
|
}
|
|
10515
|
-
await appendLinkSuggestions(entryId, lines);
|
|
10516
10323
|
try {
|
|
10517
10324
|
const wsForTracking = await getWorkspaceContext();
|
|
10518
10325
|
trackQualityCheck(wsForTracking.workspaceId, {
|
|
@@ -10541,9 +10348,7 @@ Entry \`${entryId}\` not found. Use \`search\` to find the right ID.`,
|
|
|
10541
10348
|
...res.stale ? {} : { passed: verdict.passed },
|
|
10542
10349
|
criteria: res.stale ? [] : criteria.map((c) => ({ id: c.id, passed: c.passed, hint: c.hint }))
|
|
10543
10350
|
},
|
|
10544
|
-
[
|
|
10545
|
-
{ tool: "relations", description: "Suggest connections", parameters: { action: "suggest", entryId } }
|
|
10546
|
-
]
|
|
10351
|
+
[]
|
|
10547
10352
|
);
|
|
10548
10353
|
}
|
|
10549
10354
|
async function handleReEvaluate(entryId, context) {
|
|
@@ -10632,25 +10437,6 @@ function composeWrapupView(raw) {
|
|
|
10632
10437
|
}
|
|
10633
10438
|
|
|
10634
10439
|
// src/tools/wrapup.ts
|
|
10635
|
-
async function mapWithConcurrency(items, mapper, concurrency = 3) {
|
|
10636
|
-
const results = new Array(items.length);
|
|
10637
|
-
let index = 0;
|
|
10638
|
-
async function runNext() {
|
|
10639
|
-
while (index < items.length) {
|
|
10640
|
-
const i = index++;
|
|
10641
|
-
try {
|
|
10642
|
-
const value = await mapper(items[i]);
|
|
10643
|
-
results[i] = { status: "fulfilled", value };
|
|
10644
|
-
} catch (reason) {
|
|
10645
|
-
results[i] = { status: "rejected", reason };
|
|
10646
|
-
}
|
|
10647
|
-
}
|
|
10648
|
-
}
|
|
10649
|
-
await Promise.all(
|
|
10650
|
-
Array.from({ length: Math.min(concurrency, items.length) }, () => runNext())
|
|
10651
|
-
);
|
|
10652
|
-
return results;
|
|
10653
|
-
}
|
|
10654
10440
|
function isValidTopGap(g) {
|
|
10655
10441
|
if (typeof g !== "object" || g === null) return false;
|
|
10656
10442
|
const obj = g;
|
|
@@ -10672,40 +10458,13 @@ async function fetchSessionCoherenceSnapshot(sessionId) {
|
|
|
10672
10458
|
}
|
|
10673
10459
|
return null;
|
|
10674
10460
|
}
|
|
10675
|
-
async function suggestLinksForEntries(entries) {
|
|
10676
|
-
const suggestions = [];
|
|
10677
|
-
const results = await mapWithConcurrency(
|
|
10678
|
-
entries,
|
|
10679
|
-
async (entry) => {
|
|
10680
|
-
const result2 = await kernelQuery("chain.graphSuggestLinks", {
|
|
10681
|
-
entryId: entry.entryId,
|
|
10682
|
-
limit: 3
|
|
10683
|
-
});
|
|
10684
|
-
return { entry, suggestions: result2.suggestions ?? [] };
|
|
10685
|
-
},
|
|
10686
|
-
3
|
|
10687
|
-
);
|
|
10688
|
-
for (const result2 of results) {
|
|
10689
|
-
if (result2.status !== "fulfilled") continue;
|
|
10690
|
-
const { entry, suggestions: entrySuggestions } = result2.value;
|
|
10691
|
-
for (const s of entrySuggestions.filter((x) => x.entryId != null).slice(0, 2)) {
|
|
10692
|
-
suggestions.push({
|
|
10693
|
-
fromEntryId: entry.entryId,
|
|
10694
|
-
toEntryId: s.entryId,
|
|
10695
|
-
type: s.recommendedRelationType,
|
|
10696
|
-
label: `${entry.entryId} \u2192 ${s.entryId} (${s.name}) [${s.recommendedRelationType}]`
|
|
10697
|
-
});
|
|
10698
|
-
}
|
|
10699
|
-
}
|
|
10700
|
-
return suggestions;
|
|
10701
|
-
}
|
|
10702
10461
|
async function runWrapupReview() {
|
|
10703
10462
|
const sessionId = getAgentSessionId();
|
|
10704
10463
|
if (!sessionId) {
|
|
10705
|
-
return { text: "No active agent session. Call `session action=start` first.", data: null,
|
|
10464
|
+
return { text: "No active agent session. Call `session action=start` first.", data: null, failureCode: "SESSION_REQUIRED" };
|
|
10706
10465
|
}
|
|
10707
10466
|
if (getApiKeyScope() === "read") {
|
|
10708
|
-
return { text: "Read-only session \u2014 nothing to review.", data: null,
|
|
10467
|
+
return { text: "Read-only session \u2014 nothing to review.", data: null, failureCode: "READONLY_SCOPE" };
|
|
10709
10468
|
}
|
|
10710
10469
|
const { workspaceId } = await getWorkspaceContext();
|
|
10711
10470
|
const kernelEnvelope = await kernelCallEnvelope("agentKnowledge.wrapupEnvelope", {
|
|
@@ -10718,10 +10477,10 @@ async function runWrapupReview() {
|
|
|
10718
10477
|
const overflowUnscanned = data.overflowUnscanned ?? 0;
|
|
10719
10478
|
const loopEmpty = data.drafts.length === 0 && overflowDraftCount === 0 && overflowUnscanned === 0;
|
|
10720
10479
|
if (data.wrapupCompletedAt && loopEmpty) {
|
|
10721
|
-
return { text: "Wrapup already completed this session.", data,
|
|
10480
|
+
return { text: "Wrapup already completed this session.", data, failureCode: "ALREADY_COMPLETED" };
|
|
10722
10481
|
}
|
|
10723
10482
|
if (data.drafts.length === 0 && data.committed.length === 0 && overflowDraftCount === 0 && (data.overflowCommitted ?? []).length === 0) {
|
|
10724
|
-
return { text: "Empty session \u2014 no entries to review.", data,
|
|
10483
|
+
return { text: "Empty session \u2014 no entries to review.", data, failureCode: "NOT_FOUND" };
|
|
10725
10484
|
}
|
|
10726
10485
|
const lines = [];
|
|
10727
10486
|
lines.push(
|
|
@@ -10766,17 +10525,6 @@ async function runWrapupReview() {
|
|
|
10766
10525
|
}
|
|
10767
10526
|
lines.push("");
|
|
10768
10527
|
}
|
|
10769
|
-
const lowConnEntries = [...data.drafts, ...data.committed].filter(
|
|
10770
|
-
(e) => (data.connectionCounts[e.entryId] ?? 0) < 2
|
|
10771
|
-
);
|
|
10772
|
-
const suggestions = lowConnEntries.length > 0 ? await suggestLinksForEntries(lowConnEntries.slice(0, 10)) : [];
|
|
10773
|
-
if (suggestions.length > 0) {
|
|
10774
|
-
lines.push("### Suggested Links (not written; wrapup-commit writes none \u2014 create the ones you want with `relations action=create`)", "");
|
|
10775
|
-
for (const s of suggestions) {
|
|
10776
|
-
lines.push(`- ${s.label}`);
|
|
10777
|
-
}
|
|
10778
|
-
lines.push("");
|
|
10779
|
-
}
|
|
10780
10528
|
const topGaps = getTopGaps(5);
|
|
10781
10529
|
if (topGaps.length > 0) {
|
|
10782
10530
|
lines.push("### Learning Opportunities", "");
|
|
@@ -10875,7 +10623,6 @@ async function runWrapupReview() {
|
|
|
10875
10623
|
return {
|
|
10876
10624
|
text: lines.join("\n"),
|
|
10877
10625
|
data,
|
|
10878
|
-
suggestions,
|
|
10879
10626
|
gapCount,
|
|
10880
10627
|
coherenceVerdict,
|
|
10881
10628
|
persistentGaps,
|
|
@@ -11008,7 +10755,7 @@ function createWrapupHandler() {
|
|
|
11008
10755
|
commitNext
|
|
11009
10756
|
);
|
|
11010
10757
|
}
|
|
11011
|
-
const { text, data,
|
|
10758
|
+
const { text, data, failureCode, gapCount, coherenceVerdict, persistentGaps, kernelSummary, kernelNext, kernelMeta } = await runWrapupReview();
|
|
11012
10759
|
lastReviewData = data;
|
|
11013
10760
|
lastReviewSessionId = getAgentSessionId();
|
|
11014
10761
|
const composed = data ? composeWrapupView(data) : null;
|
|
@@ -11025,7 +10772,7 @@ ${text}` : text;
|
|
|
11025
10772
|
const persistentSummary = persistentGaps?.length ? `, ${persistentGaps.length} persistent coherence gaps` : "";
|
|
11026
10773
|
const draftsCount = composed?.drafts.length ?? data.drafts.length;
|
|
11027
10774
|
const committedCount = composed?.committed.length ?? data.committed.length;
|
|
11028
|
-
const summaryText = `Session review: ${draftsCount} uncommitted, ${committedCount} committed
|
|
10775
|
+
const summaryText = `Session review: ${draftsCount} uncommitted, ${committedCount} committed${gapsSummary}${coherenceSummary}${persistentSummary}.`;
|
|
11029
10776
|
return {
|
|
11030
10777
|
content: [{ type: "text", text: fullText }],
|
|
11031
10778
|
structuredContent: {
|
|
@@ -11035,7 +10782,6 @@ ${text}` : text;
|
|
|
11035
10782
|
drafts: draftsCount,
|
|
11036
10783
|
committed: committedCount,
|
|
11037
10784
|
uncommitted: data.summary.uncommitted,
|
|
11038
|
-
suggestedLinks: suggestions.length,
|
|
11039
10785
|
knowledgeGaps: gapCount ?? 0,
|
|
11040
10786
|
...coherenceVerdict ? { coherenceVerdict } : {},
|
|
11041
10787
|
...persistentGaps?.length ? { persistentCoherenceGaps: persistentGaps.length } : {}
|
|
@@ -11408,7 +11154,7 @@ function registerSessionTools(server) {
|
|
|
11408
11154
|
"session",
|
|
11409
11155
|
{
|
|
11410
11156
|
title: "Session \u2014 Lifecycle & Facilitation",
|
|
11411
|
-
description: "Session lifecycle, wrapup review, and work package facilitation.\n\n- **start**: Begin a tracked write session for attribution. **This is the ONLY call that creates a session** \u2014 write tools (capture, entries action=update/commit, relations) require it. `orient action=start` orients an existing session and loads stage-aware context, but does NOT create one: call `session action=start` first, then `orient action=start`.\n- **close**: End the current session. Records structured data (entries created, modified, relations). Run `session action=wrapup-review` before closing.\n- **status**: Check current session \u2014 active or not, oriented, activity counts.\n- **wrapup-review**: Review uncommitted drafts with quality scores
|
|
11157
|
+
description: "Session lifecycle, wrapup review, and work package facilitation.\n\n- **start**: Begin a tracked write session for attribution. **This is the ONLY call that creates a session** \u2014 write tools (capture, entries action=update/commit, relations) require it. `orient action=start` orients an existing session and loads stage-aware context, but does NOT create one: call `session action=start` first, then `orient action=start`.\n- **close**: End the current session. Records structured data (entries created, modified, relations). Run `session action=wrapup-review` before closing.\n- **status**: Check current session \u2014 active or not, oriented, activity counts.\n- **wrapup-review**: Review uncommitted drafts with quality scores. Run before close.\n- **wrapup-commit**: Accept all uncommitted drafts. Writes no relations; create the ones you want with relations action=create.\n- **resume**: Load session state (work package data, constellation, drafts) from an existing work package entry.\n- **commit-constellation**: Atomically accept a work package and all its linked draft entries in one call.",
|
|
11412
11158
|
inputSchema: sessionSchema,
|
|
11413
11159
|
// Mixed read/write noun (§3 R3): wrapup-commit/resume/commit-constellation write.
|
|
11414
11160
|
annotations: { readOnlyHint: false, destructiveHint: false, openWorldHint: false }
|
|
@@ -13147,7 +12893,7 @@ var AGENT_CHEATSHEET = `# Product Brain \u2014 Agent Cheatsheet
|
|
|
13147
12893
|
| \`orient\` | Workspace context, governance, active work packages, session start | \`action\`: start, task |
|
|
13148
12894
|
| \`capture\` | Create entry | \`action\`: capture |
|
|
13149
12895
|
| \`entries\` | Read and write entries | \`action\`: list, get, batch, search, update, commit, history, move, verify, preflight, still-true |
|
|
13150
|
-
| \`relations\` | Link entries,
|
|
12896
|
+
| \`relations\` | Link entries, delete links, find connections | \`action\`: create, batch-create, delete, find |
|
|
13151
12897
|
| \`context\` | Gather related knowledge, last-verified brief | \`action\`: gather, build, neighborhood, changes, chain, cross-cut, incremental, brief, last-verified-brief |
|
|
13152
12898
|
| \`collections\` | Browse, describe, audit and export collections; manage labels | \`action\`: list, update, describe, audit, export, label-* |
|
|
13153
12899
|
| \`quality\` | Score an entry, verify the Chain, spine check | \`action\`: check, re-evaluate, verify-chain, spine-check |
|
|
@@ -13184,7 +12930,7 @@ Write via \`update\`: \`draft\` | \`archived\` (\`accepted\` via \`commit\`, nev
|
|
|
13184
12930
|
- A rule on no list binds nobody; a full list refuses a new rule until one retires, and a list over its cap is served whole.
|
|
13185
12931
|
|
|
13186
12932
|
## Key Patterns
|
|
13187
|
-
- **Capture flow:** \`capture\` \u2192 \`relations action=
|
|
12933
|
+
- **Capture flow:** \`capture\` \u2192 \`relations action=batch-create\` \u2192 \`entries action=commit\`
|
|
13188
12934
|
- Use \`workflowStatus\` (not \`status\`) for domain workflow state
|
|
13189
12935
|
- \`data\` param is merged (not replaced) \u2014 safe for partial updates
|
|
13190
12936
|
`;
|
|
@@ -13226,13 +12972,13 @@ Tags for filtering (e.g. \`severity:high\`), relations via \`entryRelations\`, v
|
|
|
13226
12972
|
'## Rules\nThe rules that bind you are in the briefing above, each with its why. A rule on no list binds nobody.\nOpen one: `entries action=get entryId="<ID>"`. Find one: `entries action=search query="<noun>"`.\nDraft a new rule: use the `draft-rule-from-context` prompt.'
|
|
13227
12973
|
);
|
|
13228
12974
|
sections.push(
|
|
13229
|
-
"## 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=
|
|
12975
|
+
"## 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- `relations 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`."
|
|
13230
12976
|
);
|
|
13231
12977
|
sections.push(
|
|
13232
12978
|
`## Creating Knowledge
|
|
13233
12979
|
**Entries:** Use \`capture\` as the primary tool for creating new entries. It handles the full workflow in one call:
|
|
13234
12980
|
1. Creates the entry with collection-aware defaults (auto-fills dates, infers domains, sets priority)
|
|
13235
|
-
2. Writes the relations you pass in \`links\` \u2014 and no others; use \`relations action=
|
|
12981
|
+
2. Writes the relations you pass in \`links\` \u2014 and no others; use \`relations action=create\` to add more
|
|
13236
12982
|
3. Returns the server's quality verdict with improvement hints
|
|
13237
12983
|
|
|
13238
12984
|
**Smart profiles** exist for: ${SMART_PROFILE_COLLECTIONS.map((c) => `\`${c}\``).join(", ")}.
|
|
@@ -13249,14 +12995,14 @@ Use \`quality action=check\` to score existing entries retroactively.
|
|
|
13249
12995
|
Use \`entries action=update\` for post-creation adjustments (status changes, field updates, deprecation).`
|
|
13250
12996
|
);
|
|
13251
12997
|
sections.push(
|
|
13252
|
-
"## Where to Go Next\n- **Create entry** \u2192 `capture` tool (entry, the links you pass, and a quality verdict in one call)\n- **Full context** \u2192 `context action=gather` (by entry ID or task description)\n- **
|
|
12998
|
+
"## Where to Go Next\n- **Create entry** \u2192 `capture` tool (entry, the links you pass, and a quality verdict in one call)\n- **Full context** \u2192 `context action=gather` (by entry ID or task description)\n- **Link entries** \u2192 `relations action=create`\n- **Quality audit** \u2192 `quality action=check`\n- **Terminology** \u2192 `name-check` prompt or `productbrain://terminology` resource\n- **Schema details** \u2192 `productbrain://collections` resource or `collections action=list`\n- **Labels** \u2192 `productbrain://labels` resource or `collections action=label-list`\n- **Any collection** \u2192 `productbrain://{slug}/entries` resource\n- **Log a decision** \u2192 `draft-decision-record` prompt\n- **Growth funnel** \u2192 `productbrain://growth-funnel` resource or `entries action=list collection=strategy status=draft`\n- **Session identity** \u2192 `workspace action=whoami`\n- **Health check** \u2192 `workspace action=check`\n- **Debug MCP calls** \u2192 `workspace action=audit`"
|
|
13253
12999
|
);
|
|
13254
13000
|
return sections.join("\n\n---\n\n");
|
|
13255
13001
|
}
|
|
13256
|
-
async function buildOrientationResourceText(
|
|
13002
|
+
async function buildOrientationResourceText(kernelQuery3, agentSessionId) {
|
|
13257
13003
|
const [collectionsResult, orientViewResult] = await Promise.allSettled([
|
|
13258
|
-
|
|
13259
|
-
|
|
13004
|
+
kernelQuery3("chain.listCollections"),
|
|
13005
|
+
kernelQuery3("chain.getOrientView", {
|
|
13260
13006
|
tier: "summary",
|
|
13261
13007
|
invocationPath: "unknown",
|
|
13262
13008
|
...agentSessionId ? { sessionId: agentSessionId } : {}
|
|
@@ -13837,10 +13583,10 @@ var INSTRUCTIONS = [
|
|
|
13837
13583
|
"",
|
|
13838
13584
|
"## Writing",
|
|
13839
13585
|
"",
|
|
13840
|
-
"Write actions (capture, entries update and commit, relations create, batch-create
|
|
13586
|
+
"Write actions (capture, entries update and commit, relations create, batch-create and delete) need an active session and a readwrite key.",
|
|
13841
13587
|
"Pass the `sessionId` that `session action=start` returned on every write: the connection does not remember it.",
|
|
13842
13588
|
"Use `capture` to create an entry and read its reply: an entry that came back accepted needs no `entries action=commit`.",
|
|
13843
|
-
"Link with `relations action=
|
|
13589
|
+
"Link entries with `relations action=create` \u2014 a relation is written only when you ask, with exactly the verb you send.",
|
|
13844
13590
|
"Finish with `session action=wrapup-review`, then `session action=close`.",
|
|
13845
13591
|
"If the work falls outside the active work packages, name the gap and ask the user before designing it.",
|
|
13846
13592
|
"",
|
|
@@ -13938,4 +13684,4 @@ export {
|
|
|
13938
13684
|
createProductBrainServer,
|
|
13939
13685
|
initFeatureFlags
|
|
13940
13686
|
};
|
|
13941
|
-
//# sourceMappingURL=chunk-
|
|
13687
|
+
//# sourceMappingURL=chunk-OEONSR7Q.js.map
|