@productbrain/mcp 0.0.1-beta.1296 → 0.0.1-beta.1323

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.
@@ -1091,41 +1091,6 @@ async function isWorkPlanningCollection(slug) {
1091
1091
  }
1092
1092
 
1093
1093
  // src/tools/smart-capture.ts
1094
- function normalizeMatchText(value) {
1095
- return value.toLowerCase().replace(/[^a-z0-9]+/g, " ").replace(/\s+/g, " ").trim();
1096
- }
1097
- function tokenizeMatchText(value) {
1098
- return normalizeMatchText(value).split(" ").filter((token) => token.length >= 3 && !STOP_WORDS.has(token));
1099
- }
1100
- function scoreOptionMatch(normalizedText, textTokens, option) {
1101
- const normalizedOption = normalizeMatchText(option);
1102
- if (!normalizedOption) return 0;
1103
- if (normalizedText.includes(normalizedOption)) return 100;
1104
- const optionTokens = tokenizeMatchText(option);
1105
- if (optionTokens.length === 0) return 0;
1106
- const matchedTokens = optionTokens.filter((token) => textTokens.has(token)).length;
1107
- if (matchedTokens === 0) return 0;
1108
- const ratio = matchedTokens / optionTokens.length;
1109
- return ratio >= 0.5 ? ratio * 10 : 0;
1110
- }
1111
- function inferFieldOption(text, collectionFields, fieldKeys) {
1112
- const normalizedText = normalizeMatchText(text);
1113
- if (!normalizedText) return "";
1114
- const textTokens = new Set(tokenizeMatchText(text));
1115
- let bestOption = "";
1116
- let bestScore = 0;
1117
- for (const field of collectionFields) {
1118
- if (!fieldKeys.includes(field.key) || !field.options?.length) continue;
1119
- for (const option of field.options) {
1120
- const score = scoreOptionMatch(normalizedText, textTokens, option);
1121
- if (score > bestScore) {
1122
- bestScore = score;
1123
- bestOption = option;
1124
- }
1125
- }
1126
- }
1127
- return bestOption;
1128
- }
1129
1094
  var COMMON_CHECKS = {
1130
1095
  clearName: {
1131
1096
  id: "clear-name",
@@ -1161,414 +1126,7 @@ var COMMON_CHECKS = {
1161
1126
  suggestion: () => "Classify this entry with a canonical type for better context assembly. Use update-entry to set canonicalKey."
1162
1127
  }
1163
1128
  };
1164
- var STRATEGY_CATEGORY_INFERENCE_THRESHOLD = 2;
1165
- var STRATEGY_CATEGORY_SIGNALS = [
1166
- ["business-model", [/pricing/, /revenue/, /unit economics/, /cost structure/, /monetiz/, /margin/, /per.?seat/, /subscription/, /freemium/]],
1167
- ["vision", [/vision/, /aspirational/, /future state/, /world where/]],
1168
- ["purpose", [/purpose/, /why we exist/, /mission/, /reason for being/]],
1169
- ["goal", [/goal/, /metric/, /target/, /kpi/, /okr/, /critical number/, /measur/]],
1170
- ["principle", [/we believe/, /guiding principle/, /core belief/, /philosophy/]],
1171
- ["product-area", [/product area/, /module/, /surface/, /capability area/]],
1172
- ["audience", [/audience/, /persona/, /user segment/, /icp/, /target market/]],
1173
- ["insight", [/insight/, /we learned/, /we observed/, /pattern/]],
1174
- ["opportunity", [/opportunity/, /whitespace/, /gap/, /underserved/]]
1175
- ];
1176
- function inferStrategyCategory(name, description) {
1177
- const text = `${name} ${description}`.toLowerCase();
1178
- let bestCategory = null;
1179
- let bestScore = 0;
1180
- for (const [cat, signals] of STRATEGY_CATEGORY_SIGNALS) {
1181
- const score = signals.reduce((s, rx) => s + (rx.test(text) ? 1 : 0), 0);
1182
- if (score > bestScore) {
1183
- bestScore = score;
1184
- bestCategory = cat;
1185
- }
1186
- }
1187
- if (bestCategory && bestScore >= STRATEGY_CATEGORY_INFERENCE_THRESHOLD) return bestCategory;
1188
- if (bestScore === 0) return "strategy";
1189
- return null;
1190
- }
1191
- var PROFILE_ENRICHMENTS = /* @__PURE__ */ new Map([
1192
- ["tensions", {
1193
- governedDraft: false,
1194
- descriptionField: "description",
1195
- defaults: [
1196
- { key: "priority", value: "medium" },
1197
- { key: "raised", value: "infer" },
1198
- { key: "severity", value: "infer" }
1199
- ],
1200
- recommendedRelationTypes: ["surfaces_tension_in", "references", "belongs_to", "related_to"],
1201
- inferField: (ctx) => {
1202
- const fields = {};
1203
- const text = `${ctx.name} ${ctx.description}`;
1204
- const raised = inferFieldOption(text, ctx.collectionFields, ["raised"]);
1205
- const affectedArea = inferFieldOption(text, ctx.collectionFields, ["affectedArea", "area"]);
1206
- if (raised) fields.raised = raised;
1207
- if (text.toLowerCase().includes("critical") || text.toLowerCase().includes("blocker")) {
1208
- fields.severity = "critical";
1209
- } else if (text.toLowerCase().includes("bottleneck") || text.toLowerCase().includes("scaling") || text.toLowerCase().includes("breaking")) {
1210
- fields.severity = "high";
1211
- } else {
1212
- fields.severity = "medium";
1213
- }
1214
- if (affectedArea) fields.affectedArea = affectedArea;
1215
- return fields;
1216
- },
1217
- qualityChecks: [
1218
- COMMON_CHECKS.clearName,
1219
- COMMON_CHECKS.hasDescription,
1220
- COMMON_CHECKS.hasRelations,
1221
- COMMON_CHECKS.hasType,
1222
- {
1223
- id: "has-severity",
1224
- label: "Severity specified",
1225
- check: (ctx) => !!ctx.data.severity && ctx.data.severity !== "",
1226
- suggestion: (ctx) => {
1227
- const text = `${ctx.name} ${ctx.description}`.toLowerCase();
1228
- const inferred = text.includes("critical") ? "critical" : text.includes("bottleneck") ? "high" : "medium";
1229
- return `Set severity \u2014 suggest: ${inferred} (based on description keywords).`;
1230
- }
1231
- },
1232
- {
1233
- id: "has-affected-area",
1234
- label: "Affected area identified",
1235
- check: (ctx) => !!ctx.data.affectedArea && ctx.data.affectedArea !== "",
1236
- suggestion: (ctx) => {
1237
- const area = inferFieldOption(`${ctx.name} ${ctx.description}`, ctx.collectionFields, ["affectedArea", "area"]);
1238
- return area ? `Set affectedArea \u2014 suggest: "${area}" (inferred from content).` : "Specify which product area or domain this tension impacts.";
1239
- }
1240
- }
1241
- ]
1242
- }],
1243
- ["business-rules", {
1244
- governedDraft: true,
1245
- descriptionField: "description",
1246
- defaults: [
1247
- { key: "severity", value: "medium" },
1248
- { key: "domain", value: "infer" }
1249
- ],
1250
- recommendedRelationTypes: ["governs", "references", "conflicts_with", "related_to"],
1251
- inferField: (ctx) => {
1252
- const fields = {};
1253
- const domain = inferFieldOption(`${ctx.name} ${ctx.description}`, ctx.collectionFields, ["domain"]);
1254
- if (domain) fields.domain = domain;
1255
- return fields;
1256
- },
1257
- qualityChecks: [
1258
- COMMON_CHECKS.clearName,
1259
- COMMON_CHECKS.hasDescription,
1260
- COMMON_CHECKS.hasRelations,
1261
- COMMON_CHECKS.hasType,
1262
- {
1263
- id: "has-rationale",
1264
- label: "Rationale provided",
1265
- check: (ctx) => typeof ctx.data.rationale === "string" && ctx.data.rationale.length > 10,
1266
- suggestion: () => "Add a rationale explaining why this rule exists via `update-entry`."
1267
- },
1268
- {
1269
- id: "has-domain",
1270
- label: "Domain specified",
1271
- check: (ctx) => !!ctx.data.domain && ctx.data.domain !== "",
1272
- suggestion: (ctx) => {
1273
- const domain = inferFieldOption(`${ctx.name} ${ctx.description}`, ctx.collectionFields, ["domain"]);
1274
- return domain ? `Set domain \u2014 suggest: "${domain}" (inferred from content).` : "Specify the business domain this rule belongs to.";
1275
- }
1276
- }
1277
- ]
1278
- }],
1279
- ["glossary", {
1280
- governedDraft: true,
1281
- descriptionField: "canonical",
1282
- defaults: [
1283
- { key: "category", value: "infer" }
1284
- ],
1285
- recommendedRelationTypes: ["defines_term_for", "confused_with", "related_to", "references"],
1286
- inferField: (ctx) => {
1287
- const fields = {};
1288
- const category = inferFieldOption(`${ctx.name} ${ctx.description}`, ctx.collectionFields, ["category"]);
1289
- if (category) fields.category = category;
1290
- return fields;
1291
- },
1292
- qualityChecks: [
1293
- COMMON_CHECKS.clearName,
1294
- COMMON_CHECKS.hasType,
1295
- {
1296
- id: "has-canonical",
1297
- label: "Canonical definition provided (>20 chars)",
1298
- check: (ctx) => {
1299
- const canonical = ctx.data.canonical;
1300
- return typeof canonical === "string" && canonical.length > 20;
1301
- },
1302
- suggestion: () => "Add a clear canonical definition \u2014 this is the single source of truth for this term."
1303
- },
1304
- COMMON_CHECKS.hasRelations,
1305
- {
1306
- id: "has-category",
1307
- label: "Category assigned",
1308
- check: (ctx) => !!ctx.data.category && ctx.data.category !== "",
1309
- suggestion: () => "Assign a category (e.g., 'Platform & Architecture', 'Governance & Process')."
1310
- }
1311
- ]
1312
- }],
1313
- ["decisions", {
1314
- governedDraft: false,
1315
- descriptionField: "rationale",
1316
- defaults: [
1317
- { key: "decidedBy", value: "infer" },
1318
- { key: "confidence", value: "exploring" }
1319
- ],
1320
- recommendedRelationTypes: ["informs", "references", "replaces", "related_to"],
1321
- inferField: (ctx) => {
1322
- const fields = {};
1323
- const decidedBy = inferFieldOption(`${ctx.name} ${ctx.description}`, ctx.collectionFields, ["decidedBy", "owner"]);
1324
- if (decidedBy) fields.decidedBy = decidedBy;
1325
- return fields;
1326
- },
1327
- qualityChecks: [
1328
- COMMON_CHECKS.clearName,
1329
- COMMON_CHECKS.hasType,
1330
- {
1331
- id: "has-rationale",
1332
- label: "Rationale provided (>30 chars)",
1333
- check: (ctx) => {
1334
- const rationale = ctx.data.rationale;
1335
- return typeof rationale === "string" && rationale.length > 30;
1336
- },
1337
- suggestion: () => "Explain why this decision was made \u2014 what was considered and rejected?"
1338
- },
1339
- COMMON_CHECKS.hasRelations,
1340
- {
1341
- id: "has-date",
1342
- label: "Decision date recorded",
1343
- check: (ctx) => !!ctx.data.date && ctx.data.date !== "",
1344
- suggestion: () => "Record when this decision was made."
1345
- }
1346
- ]
1347
- }],
1348
- ["features", {
1349
- governedDraft: false,
1350
- descriptionField: "description",
1351
- defaults: [],
1352
- recommendedRelationTypes: ["belongs_to", "depends_on", "surfaces_tension_in", "related_to"],
1353
- qualityChecks: [
1354
- COMMON_CHECKS.clearName,
1355
- COMMON_CHECKS.hasDescription,
1356
- COMMON_CHECKS.hasRelations,
1357
- COMMON_CHECKS.hasType,
1358
- {
1359
- id: "has-owner",
1360
- label: "Owner assigned",
1361
- check: (ctx) => !!ctx.data.owner && ctx.data.owner !== "",
1362
- suggestion: () => "Assign an owner team or product area."
1363
- },
1364
- {
1365
- id: "has-rationale",
1366
- label: "Rationale documented",
1367
- check: (ctx) => !!ctx.data.rationale && String(ctx.data.rationale).length > 20,
1368
- suggestion: () => "Explain why this feature matters \u2014 what problem does it solve?"
1369
- }
1370
- ]
1371
- }],
1372
- ["audiences", {
1373
- governedDraft: false,
1374
- descriptionField: "description",
1375
- defaults: [],
1376
- recommendedRelationTypes: ["fills_slot", "informs", "related_to", "references"],
1377
- qualityChecks: [
1378
- COMMON_CHECKS.clearName,
1379
- COMMON_CHECKS.hasDescription,
1380
- COMMON_CHECKS.hasRelations,
1381
- COMMON_CHECKS.hasType,
1382
- {
1383
- id: "has-behaviors",
1384
- label: "Behaviors described",
1385
- check: (ctx) => typeof ctx.data.behaviors === "string" && ctx.data.behaviors.length > 20,
1386
- suggestion: () => "Describe how this audience segment behaves \u2014 what do they do, what tools do they use?"
1387
- }
1388
- ]
1389
- }],
1390
- ["strategy", {
1391
- governedDraft: true,
1392
- descriptionField: "description",
1393
- defaults: [],
1394
- recommendedRelationTypes: ["informs", "governs", "belongs_to", "related_to"],
1395
- inferField: (ctx) => {
1396
- if (ctx.data?.category) return {};
1397
- const category = inferStrategyCategory(ctx.name, ctx.description);
1398
- return category ? { category } : {};
1399
- },
1400
- qualityChecks: [
1401
- COMMON_CHECKS.clearName,
1402
- COMMON_CHECKS.hasDescription,
1403
- COMMON_CHECKS.hasRelations,
1404
- COMMON_CHECKS.hasType,
1405
- COMMON_CHECKS.diverseRelations
1406
- ]
1407
- }],
1408
- ["maps", {
1409
- governedDraft: false,
1410
- descriptionField: "description",
1411
- defaults: [],
1412
- recommendedRelationTypes: ["fills_slot", "references", "related_to"],
1413
- qualityChecks: [
1414
- COMMON_CHECKS.clearName,
1415
- COMMON_CHECKS.hasDescription
1416
- ]
1417
- }],
1418
- ["chains", {
1419
- governedDraft: false,
1420
- descriptionField: "description",
1421
- defaults: [],
1422
- recommendedRelationTypes: ["informs", "references", "related_to"],
1423
- qualityChecks: [
1424
- COMMON_CHECKS.clearName,
1425
- COMMON_CHECKS.hasDescription
1426
- ]
1427
- }],
1428
- ["work-packages", {
1429
- governedDraft: false,
1430
- descriptionField: "description",
1431
- defaults: [],
1432
- recommendedRelationTypes: ["part_of", "depends_on", "blocks", "informs", "references", "related_to"],
1433
- qualityChecks: [
1434
- COMMON_CHECKS.clearName,
1435
- COMMON_CHECKS.hasDescription,
1436
- COMMON_CHECKS.hasRelations
1437
- ]
1438
- }],
1439
- ["principles", {
1440
- governedDraft: true,
1441
- descriptionField: "description",
1442
- defaults: [
1443
- { key: "severity", value: "high" },
1444
- { key: "category", value: "infer" }
1445
- ],
1446
- recommendedRelationTypes: ["governs", "informs", "references", "related_to"],
1447
- inferField: (ctx) => {
1448
- const fields = {};
1449
- const category = inferFieldOption(`${ctx.name} ${ctx.description}`, ctx.collectionFields, ["category"]);
1450
- if (category) fields.category = category;
1451
- return fields;
1452
- },
1453
- qualityChecks: [
1454
- COMMON_CHECKS.clearName,
1455
- COMMON_CHECKS.hasDescription,
1456
- COMMON_CHECKS.hasRelations,
1457
- COMMON_CHECKS.hasType,
1458
- {
1459
- id: "has-rationale",
1460
- label: "Rationale provided \u2014 why this principle matters",
1461
- check: (ctx) => typeof ctx.data.rationale === "string" && ctx.data.rationale.length > 20,
1462
- suggestion: () => "Explain why this principle exists and what goes wrong without it."
1463
- }
1464
- ]
1465
- }],
1466
- ["standards", {
1467
- governedDraft: true,
1468
- descriptionField: "description",
1469
- defaults: [],
1470
- recommendedRelationTypes: ["governs", "defines_term_for", "references", "related_to"],
1471
- qualityChecks: [
1472
- COMMON_CHECKS.clearName,
1473
- COMMON_CHECKS.hasDescription,
1474
- COMMON_CHECKS.hasRelations,
1475
- COMMON_CHECKS.hasType
1476
- ]
1477
- }],
1478
- ["tracking-events", {
1479
- governedDraft: false,
1480
- descriptionField: "description",
1481
- defaults: [],
1482
- recommendedRelationTypes: ["references", "belongs_to", "related_to"],
1483
- qualityChecks: [
1484
- COMMON_CHECKS.clearName,
1485
- COMMON_CHECKS.hasDescription
1486
- ]
1487
- }],
1488
- ["insights", {
1489
- governedDraft: false,
1490
- descriptionField: "description",
1491
- defaults: [
1492
- { key: "evidenceStrength", value: "anecdotal" }
1493
- ],
1494
- recommendedRelationTypes: ["validates", "invalidates", "informs", "related_to"],
1495
- qualityChecks: [
1496
- COMMON_CHECKS.clearName,
1497
- COMMON_CHECKS.hasDescription,
1498
- COMMON_CHECKS.hasRelations,
1499
- COMMON_CHECKS.hasType,
1500
- {
1501
- id: "has-evidence-link",
1502
- label: "Evidence link exists (validates or invalidates)",
1503
- check: (ctx) => ctx.linksCreated.some((l) => l.relationType === "validates" || l.relationType === "invalidates"),
1504
- suggestion: () => "Link this insight to the assumption or claim it validates/invalidates using `relations action=create type=validates`."
1505
- },
1506
- {
1507
- id: "has-source",
1508
- label: "Source documented",
1509
- check: (ctx) => !!ctx.data?.source && String(ctx.data.source).length > 0,
1510
- suggestion: () => "Document where this insight came from (research, data, user interview, etc.)."
1511
- }
1512
- ]
1513
- }],
1514
- ["assumptions", {
1515
- governedDraft: false,
1516
- descriptionField: "belief",
1517
- defaults: [
1518
- { key: "risk", value: "medium" },
1519
- { key: "evidenceStrength", value: "unvalidated" }
1520
- ],
1521
- recommendedRelationTypes: ["depends_on", "informs", "related_to", "validates", "invalidates"],
1522
- qualityChecks: [
1523
- COMMON_CHECKS.clearName,
1524
- {
1525
- id: "has-belief",
1526
- label: "Belief statement provided (>30 chars)",
1527
- check: (ctx) => !!ctx.data?.belief && String(ctx.data.belief).length > 30,
1528
- suggestion: () => "Write the assumption as a clear, testable belief statement."
1529
- },
1530
- COMMON_CHECKS.hasRelations,
1531
- COMMON_CHECKS.hasType,
1532
- {
1533
- id: "has-test-method",
1534
- label: "Test method described",
1535
- check: (ctx) => !!ctx.data?.testMethod && String(ctx.data.testMethod).length > 0,
1536
- suggestion: () => "Describe how this assumption could be tested or validated."
1537
- }
1538
- ]
1539
- }],
1540
- ["architecture", {
1541
- governedDraft: true,
1542
- descriptionField: "description",
1543
- defaults: [],
1544
- recommendedRelationTypes: ["belongs_to", "depends_on", "governs", "references", "related_to"],
1545
- qualityChecks: [
1546
- COMMON_CHECKS.clearName,
1547
- COMMON_CHECKS.hasDescription,
1548
- COMMON_CHECKS.hasRelations,
1549
- COMMON_CHECKS.hasType,
1550
- {
1551
- id: "has-layer",
1552
- label: "Architecture layer identified",
1553
- check: (ctx) => !!ctx.data?.layer && String(ctx.data.layer).length > 0,
1554
- suggestion: () => "Specify which architecture layer this belongs to (L1-L7)."
1555
- }
1556
- ]
1557
- }],
1558
- ["landscape", {
1559
- governedDraft: false,
1560
- descriptionField: "description",
1561
- defaults: [],
1562
- recommendedRelationTypes: ["references", "related_to", "fills_slot"],
1563
- qualityChecks: [
1564
- COMMON_CHECKS.clearName,
1565
- COMMON_CHECKS.hasDescription,
1566
- COMMON_CHECKS.hasRelations,
1567
- COMMON_CHECKS.hasType
1568
- ]
1569
- }]
1570
- ]);
1571
- var FALLBACK_PROFILE = {
1129
+ var GENERIC_PROFILE = {
1572
1130
  governedDraft: false,
1573
1131
  descriptionField: "description",
1574
1132
  defaults: [],
@@ -1580,54 +1138,84 @@ var FALLBACK_PROFILE = {
1580
1138
  COMMON_CHECKS.hasType
1581
1139
  ]
1582
1140
  };
1141
+ function buildFieldQualityChecks(col) {
1142
+ const checks = [];
1143
+ const fields = col.fields ?? [];
1144
+ const seen = /* @__PURE__ */ new Set();
1145
+ const addCheck = (check) => {
1146
+ if (seen.has(check.id)) return;
1147
+ seen.add(check.id);
1148
+ checks.push(check);
1149
+ };
1150
+ const labelFor = (fieldKey) => {
1151
+ return fields.find((field) => field.key === fieldKey)?.label ?? fieldKey;
1152
+ };
1153
+ for (const field of fields) {
1154
+ if (field.required === true) {
1155
+ addCheck({
1156
+ id: `field-${field.key}-required`,
1157
+ label: `${field.key} provided`,
1158
+ check: (ctx) => isNonEmptyValue(ctx.data[field.key]),
1159
+ suggestion: () => `Fill in ${labelFor(field.key)}.`
1160
+ });
1161
+ }
1162
+ if (typeof field.minLength === "number") {
1163
+ addCheck({
1164
+ id: `field-${field.key}-min-length`,
1165
+ label: `${field.key} min ${field.minLength} chars`,
1166
+ check: (ctx) => typeof ctx.data[field.key] === "string" && ctx.data[field.key].length >= field.minLength,
1167
+ suggestion: () => `Add more detail to ${labelFor(field.key)} (minimum ${field.minLength} characters).`
1168
+ });
1169
+ }
1170
+ if (typeof field.maxLength === "number") {
1171
+ addCheck({
1172
+ id: `field-${field.key}-max-length`,
1173
+ label: `${field.key} max ${field.maxLength} chars`,
1174
+ check: (ctx) => typeof ctx.data[field.key] !== "string" || ctx.data[field.key].length <= field.maxLength,
1175
+ suggestion: () => `Shorten ${labelFor(field.key)} to ${field.maxLength} characters or fewer.`
1176
+ });
1177
+ }
1178
+ }
1179
+ for (const criterion of col.qualityCriteria ?? []) {
1180
+ if (criterion.active === false) continue;
1181
+ const id = criterion.rule === "required" ? `field-${criterion.field}-required` : criterion.rule === "min_length" ? `field-${criterion.field}-min-length` : `quality-${criterion.field}-${criterion.rule}`;
1182
+ addCheck({
1183
+ id,
1184
+ label: `${criterion.field} ${criterion.rule === "required" ? "provided" : criterion.rule === "min_length" ? `min ${criterion.value} chars` : `matches ${criterion.value}`}`,
1185
+ check: (ctx) => {
1186
+ const val = ctx.data[criterion.field];
1187
+ if (criterion.rule === "required") return isNonEmptyValue(val);
1188
+ if (criterion.rule === "min_length") return typeof val === "string" && val.length >= Number(criterion.value ?? 0);
1189
+ if (criterion.rule === "pattern") return typeof val === "string" && new RegExp(criterion.value ?? "").test(val);
1190
+ return true;
1191
+ },
1192
+ suggestion: () => `Fill in ${labelFor(criterion.field)} (${criterion.rule}).`
1193
+ });
1194
+ }
1195
+ return checks;
1196
+ }
1583
1197
  async function buildRuntimeProfile(slug) {
1584
1198
  const col = await getCollectionBySlug(slug);
1585
- const enrichment = PROFILE_ENRICHMENTS.get(slug);
1586
- if (!col && !enrichment) return FALLBACK_PROFILE;
1587
- const governedDraft = col?.governed ?? enrichment?.governedDraft ?? false;
1588
- let descriptionField = enrichment?.descriptionField ?? "description";
1589
- if (!enrichment?.descriptionField && col) {
1590
- const bodyField = col.fields.find(
1591
- (f) => f.zone === "body" || f.displayHint === "textarea"
1592
- );
1593
- if (bodyField) descriptionField = bodyField.key;
1594
- }
1595
- let qualityChecks = [
1199
+ if (!col) return GENERIC_PROFILE;
1200
+ const fields = col.fields ?? [];
1201
+ const layoutDescriptionField = fields.find(
1202
+ (field) => field.zone === "body" || field.displayHint === "textarea" || field.displayHint === "section"
1203
+ )?.key;
1204
+ const descriptionField = col.descriptionFieldKey || layoutDescriptionField || "description";
1205
+ const fieldChecks = buildFieldQualityChecks(col);
1206
+ const qualityChecks = [
1596
1207
  COMMON_CHECKS.clearName,
1597
1208
  COMMON_CHECKS.hasDescription,
1209
+ ...fieldChecks,
1598
1210
  COMMON_CHECKS.hasRelations,
1599
1211
  COMMON_CHECKS.hasType
1600
1212
  ];
1601
- if (enrichment?.qualityChecks) {
1602
- qualityChecks = enrichment.qualityChecks;
1603
- } else if (col?.qualityCriteria?.length) {
1604
- const dbChecks = col.qualityCriteria.map((qc) => ({
1605
- id: `db-${qc.field}-${qc.rule}`,
1606
- label: `${qc.field} ${qc.rule === "required" ? "provided" : qc.rule === "min_length" ? `min ${qc.value} chars` : `matches ${qc.value}`}`,
1607
- check: (ctx) => {
1608
- const val = ctx.data[qc.field];
1609
- if (qc.rule === "required") return val !== void 0 && val !== null && val !== "";
1610
- if (qc.rule === "min_length") return typeof val === "string" && val.length >= Number(qc.value ?? 0);
1611
- if (qc.rule === "pattern") return typeof val === "string" && new RegExp(qc.value ?? "").test(val);
1612
- return true;
1613
- },
1614
- suggestion: () => `Fill in ${qc.field} (${qc.rule}).`
1615
- }));
1616
- qualityChecks = [
1617
- COMMON_CHECKS.clearName,
1618
- COMMON_CHECKS.hasDescription,
1619
- ...dbChecks,
1620
- COMMON_CHECKS.hasRelations,
1621
- COMMON_CHECKS.hasType
1622
- ];
1623
- }
1624
1213
  return {
1625
- governedDraft,
1214
+ governedDraft: col.governed === true,
1626
1215
  descriptionField,
1627
- defaults: enrichment?.defaults ?? [],
1628
- recommendedRelationTypes: enrichment?.recommendedRelationTypes ?? FALLBACK_PROFILE.recommendedRelationTypes,
1629
- qualityChecks,
1630
- inferField: enrichment?.inferField
1216
+ defaults: [],
1217
+ recommendedRelationTypes: GENERIC_PROFILE.recommendedRelationTypes,
1218
+ qualityChecks
1631
1219
  };
1632
1220
  }
1633
1221
  var profileCache = null;
@@ -1679,36 +1267,9 @@ function computeLinkConfidence(candidate, sourceName, sourceDescription, sourceC
1679
1267
  const reason = reasons.length > 0 ? reasons.join(" + ") : "low relevance";
1680
1268
  return { score: finalScore, reason };
1681
1269
  }
1682
- function inferRelationType(sourceCollection, targetCollection, profile) {
1683
- const typeMap = {
1684
- tensions: {
1685
- glossary: "surfaces_tension_in",
1686
- "business-rules": "references",
1687
- strategy: "belongs_to",
1688
- features: "surfaces_tension_in",
1689
- decisions: "references"
1690
- },
1691
- "business-rules": {
1692
- glossary: "references",
1693
- features: "governs",
1694
- strategy: "belongs_to",
1695
- tensions: "references"
1696
- },
1697
- glossary: {
1698
- features: "defines_term_for",
1699
- "business-rules": "references",
1700
- strategy: "references"
1701
- },
1702
- decisions: {
1703
- features: "informs",
1704
- "business-rules": "references",
1705
- strategy: "references",
1706
- tensions: "references"
1707
- }
1708
- };
1709
- const mapped = typeMap[sourceCollection]?.[targetCollection];
1710
- const type = mapped ?? profile.recommendedRelationTypes[0] ?? "related_to";
1711
- const reason = mapped ? `collection pair (${sourceCollection} \u2192 ${targetCollection})` : `profile default (${profile.recommendedRelationTypes[0] ?? "related_to"})`;
1270
+ function inferRelationType(_sourceCollection, _targetCollection, profile) {
1271
+ const type = profile.recommendedRelationTypes[0] ?? "related_to";
1272
+ const reason = `profile default (${type})`;
1712
1273
  return { type, reason };
1713
1274
  }
1714
1275
  function scoreQuality(ctx, profile) {
@@ -5587,7 +5148,8 @@ _This helps future sessions start with better constraints and context._`
5587
5148
  )
5588
5149
  };
5589
5150
  }
5590
- const hasBindingGovernance = result2.bindingGovernance.principles.length > 0 || result2.bindingGovernance.standards.length > 0 || result2.bindingGovernance.businessRules.length > 0;
5151
+ const bindingGovernanceCollections = result2.bindingGovernance.byCollection ?? [];
5152
+ const hasBindingGovernance = bindingGovernanceCollections.some((bucket) => bucket.entries.length > 0) || result2.bindingGovernance.principles.length > 0 || result2.bindingGovernance.standards.length > 0 || result2.bindingGovernance.businessRules.length > 0;
5591
5153
  const byCollection2 = /* @__PURE__ */ new Map();
5592
5154
  for (const entry of result2.supportingContext) {
5593
5155
  const key = entry.collectionName;
@@ -5617,14 +5179,7 @@ _This helps future sessions start with better constraints and context._`
5617
5179
  lines2.push("## Binding governance");
5618
5180
  lines2.push("_These are the non-optional rules and standards that govern this task._");
5619
5181
  lines2.push("");
5620
- const governanceSections = [
5621
- ["Principles", result2.bindingGovernance.principles],
5622
- ["Standards", result2.bindingGovernance.standards],
5623
- ["Business Rules", result2.bindingGovernance.businessRules]
5624
- ];
5625
- for (const [title, entries] of governanceSections) {
5626
- if (entries.length === 0) continue;
5627
- lines2.push(`### ${title} (${entries.length})`);
5182
+ const renderGovernanceEntries = (entries) => {
5628
5183
  for (const entry of entries) {
5629
5184
  const id = entry.entryId ? `**${entry.entryId}:** ` : "";
5630
5185
  const scoreLabel = typeof entry.score === "number" ? ` ${entry.score}` : "";
@@ -5633,7 +5188,27 @@ _This helps future sessions start with better constraints and context._`
5633
5188
  lines2.push(`- ${id}${entry.name}${scoreLabel}${flagsLabel}${originLabel}`);
5634
5189
  if (entry.preview) lines2.push(` _${entry.preview}_`);
5635
5190
  }
5636
- lines2.push("");
5191
+ };
5192
+ const visibleCollectionBuckets = bindingGovernanceCollections.filter((bucket) => bucket.entries.length > 0);
5193
+ if (visibleCollectionBuckets.length > 0) {
5194
+ for (const bucket of visibleCollectionBuckets) {
5195
+ const title = bucket.collectionName ?? bucket.collectionSlug ?? "Binding governance";
5196
+ lines2.push(`### ${title} (${bucket.entries.length})`);
5197
+ renderGovernanceEntries(bucket.entries);
5198
+ lines2.push("");
5199
+ }
5200
+ } else {
5201
+ const governanceSections = [
5202
+ ["Principles", result2.bindingGovernance.principles],
5203
+ ["Standards", result2.bindingGovernance.standards],
5204
+ ["Business Rules", result2.bindingGovernance.businessRules]
5205
+ ];
5206
+ for (const [title, entries] of governanceSections) {
5207
+ if (entries.length === 0) continue;
5208
+ lines2.push(`### ${title} (${entries.length})`);
5209
+ renderGovernanceEntries(entries);
5210
+ lines2.push("");
5211
+ }
5637
5212
  }
5638
5213
  }
5639
5214
  lines2.push("## Steering context");
@@ -6450,7 +6025,7 @@ var fieldSchema = z8.object({
6450
6025
  });
6451
6026
  var collectionsSchema = z8.object({
6452
6027
  action: z8.enum(COLLECTIONS_ACTIONS).describe(
6453
- "'list': browse all collections. 'create': create a new collection. 'update': update an existing collection. 'describe': full documentation for one collection \u2014 fields, option guides, usage guidance, examples. 'audit': health report for all collections \u2014 missing classification, icon, displayHint coverage, and field schema gaps. 'export': full system_collection_definitions export with classification metadata (thinkingLayer, classificationPriority, classificationCheck, classificationSignals, governanceRole, timelineRole, canBeElementOf, descriptionFieldKey). Admin only."
6028
+ "'list': browse all collections. 'create': create a new collection. 'update': update an existing collection. 'describe': full documentation for one collection \u2014 fields, option guides, usage guidance, examples. 'audit': health report for all collections \u2014 missing classification, icon, displayHint coverage, and field schema gaps. 'export': full system_collection_definitions export with classification metadata (thinkingLayer, classificationPriority, classificationCheck, classificationSignals, governanceRole, governanceFunction, timelineRole, canBeElementOf, descriptionFieldKey). Admin only."
6454
6029
  ),
6455
6030
  slug: z8.string().optional().describe("URL-safe identifier for create/update, e.g. 'glossary', 'tech-debt'"),
6456
6031
  name: z8.string().optional().describe("Display name for create, or new name for update"),
@@ -6479,7 +6054,7 @@ function registerCollectionsTools(server) {
6479
6054
  "collections",
6480
6055
  {
6481
6056
  title: "Collections",
6482
- description: "Manage knowledge collections. Four actions:\n\n- **list**: Browse all collections \u2014 glossary, business rules, tracking events, etc. Returns slug, name, description, and field schema. Use before capture to see what exists.\n- **describe**: Full documentation for a single collection. Returns field help text, option decision guides, usage guidance, examples, and cross-references. Use when you need to understand a collection's purpose, field semantics, or option values.\n- **create**: Create a new collection. Provide slug, name, and field schema. Use when setting up a workspace or tracking a new type of knowledge.\n- **update**: Update an existing collection's name, description, purpose, icon, navGroup, or fields. Only provide the fields you want to change.\n- **audit**: Health report for all workspace collections. Checks classification metadata, icon presence, displayHint coverage per field, and field schema gaps vs system definitions. Returns total collections, count with issues, and per-collection issue list.\n- **export**: Full system_collection_definitions export as JSON. Includes all classification metadata (thinkingLayer, classificationPriority, classificationCheck, timelineRole, governanceRole, etc.). Admin only.",
6057
+ description: "Manage knowledge collections. Four actions:\n\n- **list**: Browse all collections \u2014 glossary, business rules, tracking events, etc. Returns slug, name, description, and field schema. Use before capture to see what exists.\n- **describe**: Full documentation for a single collection. Returns field help text, option decision guides, usage guidance, examples, and cross-references. Use when you need to understand a collection's purpose, field semantics, or option values.\n- **create**: Create a new collection. Provide slug, name, and field schema. Use when setting up a workspace or tracking a new type of knowledge.\n- **update**: Update an existing collection's name, description, purpose, icon, navGroup, or fields. Only provide the fields you want to change.\n- **audit**: Health report for all workspace collections. Checks classification metadata, icon presence, displayHint coverage per field, and field schema gaps vs system definitions. Returns total collections, count with issues, and per-collection issue list.\n- **export**: Full system_collection_definitions export as JSON. Includes all classification metadata (thinkingLayer, classificationPriority, classificationCheck, timelineRole, governanceRole, governanceFunction, etc.). Admin only.",
6483
6058
  inputSchema: collectionsSchema,
6484
6059
  annotations: { readOnlyHint: false, destructiveHint: true, openWorldHint: false }
6485
6060
  },
@@ -12629,39 +12204,13 @@ async function markOrientedWithSnapshotFallback(agentSessionId, coherenceSnapsho
12629
12204
  }
12630
12205
  }
12631
12206
  }
12632
- var VALID_TASK_DOMAINS = [
12633
- "auth",
12634
- "strategy",
12635
- "governance",
12636
- "architecture",
12637
- "product",
12638
- "product-design",
12639
- "product-design/ux",
12640
- "engineering",
12641
- "engineering/frontend",
12642
- "engineering/backend",
12643
- "data",
12644
- "data/analytics",
12645
- "go-to-market",
12646
- "go-to-market/sales",
12647
- "strategy/outcomes",
12648
- "product/roadmap",
12649
- "governance/principles",
12650
- "data-foundation",
12651
- "chainwork",
12652
- "capture-pipeline",
12653
- "ingestion",
12654
- "intelligence-and-operations",
12655
- "review-and-learning",
12656
- "general"
12657
- ];
12658
12207
  var orientSchema = z21.object({
12659
12208
  mode: z21.enum(["full", "brief"]).optional().default("full").describe("full = full context (default). brief = compact summary for mid-session re-orientation. Prefer using the `tier` param for depth control."),
12660
12209
  tier: z21.enum(["summary", "standard", "full"]).optional().describe(
12661
12210
  "Payload depth. Defaults to summary (~10 KB) when task is provided; standard (~256 KB) when task is absent. Pass summary, standard, or full to override."
12662
12211
  ),
12663
12212
  task: z21.string().optional().describe("Natural-language task description for task-scoped context. When provided, orient returns scored, relevant entries for the task."),
12664
- scope: z21.enum(VALID_TASK_DOMAINS).optional().describe(`Optional domain scope to filter governance to entries relevant for this domain. Valid values: ${VALID_TASK_DOMAINS.join(", ")}.`)
12213
+ scope: z21.string().optional().describe("Optional domain scope to filter governance to entries relevant for this domain. Forwarded to Convex for workspace-specific validation.")
12665
12214
  });
12666
12215
  function taskGroundingWarningLines(task, hasTaskGrounding = false) {
12667
12216
  if (hasTaskGrounding) return [];
@@ -12687,7 +12236,7 @@ function registerOrientTool(server) {
12687
12236
  "orient",
12688
12237
  {
12689
12238
  title: "Orient \u2014 Task Grounding",
12690
- description: "Task-grounded context loader. Use AFTER `start_pb` (the canonical session opener) to load governance and context for a specific task. Returns workspace context with a single recommended next action for low-readiness workspaces, or a standup-style briefing for established workspaces.\n\n**Not the session opener.** For 'how do I begin a session' or 'Start PB', call `start_pb` instead. `orient` is for refreshing or scoping context once a session is already underway.\n\nCompleting orientation unlocks write tools for the active session.\n\n**tier:** Controls payload depth.\n- `summary` (~10 KB) \u2014 compact, calibrated for task-scoped retrieval.\n- `standard` (~256 KB) \u2014 rich, suited to workspace standups.\n- `full` \u2014 complete payload, no cap (use with deliberation).\n\nDefaults: `summary` when `task` is provided; `standard` when `task` is absent. Explicit `tier` always wins. Pair with `mode='brief'` for compact formatting (orthogonal to depth).\n\n**mode:** `full` (default) returns full context. `brief` returns compact summary \u2014 mapped to tier=summary internally. Prefer `tier` for explicit depth control.\n\n**task:** Optional natural-language task description. When provided, returns task-scoped context (scored, relevant entries) in addition to standard orient sections.\n\n**scope:** Optional domain scope. Filters governance entries to those relevant for the specified domain. Authority roots include strategy, product, product-design, engineering, architecture, data, governance, and go-to-market. Child scopes such as product-design/ux, engineering/frontend, data/analytics, and governance/principles are accepted. Legacy internal scopes remain accepted.",
12239
+ description: "Task-grounded context loader. Use AFTER `start_pb` (the canonical session opener) to load governance and context for a specific task. Returns workspace context with a single recommended next action for low-readiness workspaces, or a standup-style briefing for established workspaces.\n\n**Not the session opener.** For 'how do I begin a session' or 'Start PB', call `start_pb` instead. `orient` is for refreshing or scoping context once a session is already underway.\n\nCompleting orientation unlocks write tools for the active session.\n\n**tier:** Controls payload depth.\n- `summary` (~10 KB) \u2014 compact, calibrated for task-scoped retrieval.\n- `standard` (~256 KB) \u2014 rich, suited to workspace standups.\n- `full` \u2014 complete payload, no cap (use with deliberation).\n\nDefaults: `summary` when `task` is provided; `standard` when `task` is absent. Explicit `tier` always wins. Pair with `mode='brief'` for compact formatting (orthogonal to depth).\n\n**mode:** `full` (default) returns full context. `brief` returns compact summary \u2014 mapped to tier=summary internally. Prefer `tier` for explicit depth control.\n\n**task:** Optional natural-language task description. When provided, returns task-scoped context (scored, relevant entries) in addition to standard orient sections.\n\n**scope:** Optional domain scope. Filters governance entries to those relevant for the specified domain. Forwarded to Convex for workspace-specific validation and custom domain resolution.",
12691
12240
  inputSchema: orientSchema,
12692
12241
  annotations: { readOnlyHint: true, idempotentHint: true, openWorldHint: false }
12693
12242
  },
@@ -15294,4 +14843,4 @@ export {
15294
14843
  createProductBrainServer,
15295
14844
  initFeatureFlags
15296
14845
  };
15297
- //# sourceMappingURL=chunk-NVEL46AW.js.map
14846
+ //# sourceMappingURL=chunk-JFJWNU2F.js.map