@productbrain/mcp 0.0.1-beta.2100 → 0.0.1-beta.2115
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.
|
@@ -675,117 +675,12 @@ async function isGoverned(slug) {
|
|
|
675
675
|
}
|
|
676
676
|
|
|
677
677
|
// src/tools/smart-capture.ts
|
|
678
|
-
var COMMON_CHECKS = {
|
|
679
|
-
clearName: {
|
|
680
|
-
id: "clear-name",
|
|
681
|
-
label: "Clear, specific name (not vague)",
|
|
682
|
-
check: (ctx) => ctx.name.length > 10 && !["new tension", "new entry", "untitled", "test"].includes(ctx.name.toLowerCase()),
|
|
683
|
-
suggestion: () => "Rename to something specific \u2014 describe the actual problem or concept."
|
|
684
|
-
},
|
|
685
|
-
hasDescription: {
|
|
686
|
-
id: "has-description",
|
|
687
|
-
label: "Description provided (>50 chars)",
|
|
688
|
-
check: (ctx) => ctx.description.length > 50,
|
|
689
|
-
suggestion: () => "Add a fuller description explaining context and impact."
|
|
690
|
-
},
|
|
691
|
-
hasRelations: {
|
|
692
|
-
id: "has-relations",
|
|
693
|
-
label: "At least 1 relation created",
|
|
694
|
-
check: (ctx) => ctx.linksCreated.length >= 1,
|
|
695
|
-
suggestion: () => "Use `graph action=suggest` and `relations action=create` to add more connections."
|
|
696
|
-
},
|
|
697
|
-
diverseRelations: {
|
|
698
|
-
id: "diverse-relations",
|
|
699
|
-
label: "Relations span multiple collections",
|
|
700
|
-
check: (ctx) => {
|
|
701
|
-
const colls = new Set(ctx.linksCreated.map((l) => l.targetCollection));
|
|
702
|
-
return colls.size >= 2;
|
|
703
|
-
},
|
|
704
|
-
suggestion: () => "Try linking to entries in different collections (glossary, business-rules, strategy)."
|
|
705
|
-
},
|
|
706
|
-
hasType: {
|
|
707
|
-
id: "has-type",
|
|
708
|
-
label: "Has canonical type",
|
|
709
|
-
check: (ctx) => !!ctx.data?.canonicalKey || !!ctx.canonicalKey,
|
|
710
|
-
suggestion: () => "Classify this entry with a canonical type for better context assembly. Use update-entry to set canonicalKey."
|
|
711
|
-
}
|
|
712
|
-
};
|
|
713
678
|
var GENERIC_PROFILE = {
|
|
714
679
|
governedDraft: false,
|
|
715
680
|
descriptionField: "description",
|
|
716
681
|
defaults: [],
|
|
717
|
-
recommendedRelationTypes: ["related_to", "references"]
|
|
718
|
-
qualityChecks: [
|
|
719
|
-
COMMON_CHECKS.clearName,
|
|
720
|
-
COMMON_CHECKS.hasDescription,
|
|
721
|
-
COMMON_CHECKS.hasRelations,
|
|
722
|
-
COMMON_CHECKS.hasType
|
|
723
|
-
]
|
|
682
|
+
recommendedRelationTypes: ["related_to", "references"]
|
|
724
683
|
};
|
|
725
|
-
var NON_DATA_CRITERION_FIELDS = /* @__PURE__ */ new Set(["grounding", "governanceWhy", "governanceSteering"]);
|
|
726
|
-
function buildFieldQualityChecks(col) {
|
|
727
|
-
const checks = [];
|
|
728
|
-
const fields = col.fields ?? [];
|
|
729
|
-
const seen = /* @__PURE__ */ new Set();
|
|
730
|
-
const addCheck = (check) => {
|
|
731
|
-
if (seen.has(check.id)) return;
|
|
732
|
-
seen.add(check.id);
|
|
733
|
-
checks.push(check);
|
|
734
|
-
};
|
|
735
|
-
const labelFor = (fieldKey) => {
|
|
736
|
-
return fields.find((field) => field.key === fieldKey)?.label ?? fieldKey;
|
|
737
|
-
};
|
|
738
|
-
for (const field of fields) {
|
|
739
|
-
if (field.required === true) {
|
|
740
|
-
addCheck({
|
|
741
|
-
id: `field-${field.key}-required`,
|
|
742
|
-
label: `${field.key} provided`,
|
|
743
|
-
check: (ctx) => isNonEmptyValue(ctx.data[field.key]),
|
|
744
|
-
suggestion: () => `Fill in ${labelFor(field.key)}.`
|
|
745
|
-
});
|
|
746
|
-
}
|
|
747
|
-
if (typeof field.minLength === "number") {
|
|
748
|
-
addCheck({
|
|
749
|
-
id: `field-${field.key}-min-length`,
|
|
750
|
-
label: `${field.key} min ${field.minLength} chars`,
|
|
751
|
-
check: (ctx) => {
|
|
752
|
-
const value = ctx.data[field.key];
|
|
753
|
-
return typeof value === "string" && value.length >= field.minLength;
|
|
754
|
-
},
|
|
755
|
-
suggestion: () => `Add more detail to ${labelFor(field.key)} (minimum ${field.minLength} characters).`
|
|
756
|
-
});
|
|
757
|
-
}
|
|
758
|
-
if (typeof field.maxLength === "number") {
|
|
759
|
-
addCheck({
|
|
760
|
-
id: `field-${field.key}-max-length`,
|
|
761
|
-
label: `${field.key} max ${field.maxLength} chars`,
|
|
762
|
-
check: (ctx) => {
|
|
763
|
-
const value = ctx.data[field.key];
|
|
764
|
-
return typeof value !== "string" || value.length <= field.maxLength;
|
|
765
|
-
},
|
|
766
|
-
suggestion: () => `Shorten ${labelFor(field.key)} to ${field.maxLength} characters or fewer.`
|
|
767
|
-
});
|
|
768
|
-
}
|
|
769
|
-
}
|
|
770
|
-
for (const criterion of col.qualityCriteria ?? []) {
|
|
771
|
-
if (criterion.active === false) continue;
|
|
772
|
-
if (NON_DATA_CRITERION_FIELDS.has(criterion.field)) continue;
|
|
773
|
-
const id = criterion.rule === "required" ? `field-${criterion.field}-required` : criterion.rule === "min_length" ? `field-${criterion.field}-min-length` : `quality-${criterion.field}-${criterion.rule}`;
|
|
774
|
-
addCheck({
|
|
775
|
-
id,
|
|
776
|
-
label: `${criterion.field} ${criterion.rule === "required" ? "provided" : criterion.rule === "min_length" ? `min ${criterion.value} chars` : `matches ${criterion.value}`}`,
|
|
777
|
-
check: (ctx) => {
|
|
778
|
-
const val = ctx.data[criterion.field];
|
|
779
|
-
if (criterion.rule === "required") return isNonEmptyValue(val);
|
|
780
|
-
if (criterion.rule === "min_length") return typeof val === "string" && val.length >= Number(criterion.value ?? 0);
|
|
781
|
-
if (criterion.rule === "pattern") return typeof val === "string" && new RegExp(criterion.value ?? "").test(val);
|
|
782
|
-
return true;
|
|
783
|
-
},
|
|
784
|
-
suggestion: () => `Fill in ${labelFor(criterion.field)} (${criterion.rule}).`
|
|
785
|
-
});
|
|
786
|
-
}
|
|
787
|
-
return checks;
|
|
788
|
-
}
|
|
789
684
|
async function buildRuntimeProfile(slug) {
|
|
790
685
|
const col = await getCollectionBySlug(slug);
|
|
791
686
|
if (!col) return GENERIC_PROFILE;
|
|
@@ -794,20 +689,11 @@ async function buildRuntimeProfile(slug) {
|
|
|
794
689
|
(field) => field.zone === "body" || field.displayHint === "textarea" || field.displayHint === "section"
|
|
795
690
|
)?.key;
|
|
796
691
|
const descriptionField = col.descriptionFieldKey || layoutDescriptionField || "description";
|
|
797
|
-
const fieldChecks = buildFieldQualityChecks(col);
|
|
798
|
-
const qualityChecks = [
|
|
799
|
-
COMMON_CHECKS.clearName,
|
|
800
|
-
COMMON_CHECKS.hasDescription,
|
|
801
|
-
...fieldChecks,
|
|
802
|
-
COMMON_CHECKS.hasRelations,
|
|
803
|
-
COMMON_CHECKS.hasType
|
|
804
|
-
];
|
|
805
692
|
return {
|
|
806
693
|
governedDraft: col.governed === true,
|
|
807
694
|
descriptionField,
|
|
808
695
|
defaults: [],
|
|
809
|
-
recommendedRelationTypes: GENERIC_PROFILE.recommendedRelationTypes
|
|
810
|
-
qualityChecks
|
|
696
|
+
recommendedRelationTypes: GENERIC_PROFILE.recommendedRelationTypes
|
|
811
697
|
};
|
|
812
698
|
}
|
|
813
699
|
var profileCache = new ScopedCache(COLLECTION_CACHE_TTL_MS);
|
|
@@ -879,85 +765,6 @@ function inferRelationType(_sourceCollection, _targetCollection, profile) {
|
|
|
879
765
|
const reason = `profile default (${type})`;
|
|
880
766
|
return { type, reason };
|
|
881
767
|
}
|
|
882
|
-
function scoreQuality(ctx, profile) {
|
|
883
|
-
const checks = profile.qualityChecks.map((qc) => {
|
|
884
|
-
const passed2 = qc.check(ctx);
|
|
885
|
-
return {
|
|
886
|
-
id: qc.id,
|
|
887
|
-
label: qc.label,
|
|
888
|
-
passed: passed2,
|
|
889
|
-
suggestion: passed2 ? void 0 : qc.suggestion?.(ctx)
|
|
890
|
-
};
|
|
891
|
-
});
|
|
892
|
-
const passed = checks.filter((c) => c.passed).length;
|
|
893
|
-
const total = checks.length;
|
|
894
|
-
const score = total > 0 ? Math.round(passed / total * 10) : 10;
|
|
895
|
-
return { score, maxScore: 10, checks };
|
|
896
|
-
}
|
|
897
|
-
function formatQualityReport(result) {
|
|
898
|
-
const failed = result.checks.filter((c) => !c.passed);
|
|
899
|
-
const reason = failed.length > 0 ? ` because ${failed.map((c) => c.suggestion ?? c.label.toLowerCase()).join("; ")}` : "";
|
|
900
|
-
const lines = [`## Quality: ${result.score}/${result.maxScore}${reason}`];
|
|
901
|
-
for (const check of result.checks) {
|
|
902
|
-
const icon = check.passed ? "[x]" : "[ ]";
|
|
903
|
-
const suggestion = check.passed ? "" : ` \u2014 ${check.suggestion ?? check.label}`;
|
|
904
|
-
lines.push(`${icon} ${check.label}${suggestion}`);
|
|
905
|
-
}
|
|
906
|
-
return lines.join("\n");
|
|
907
|
-
}
|
|
908
|
-
async function checkEntryQuality(entryId) {
|
|
909
|
-
const entry = await kernelQuery("chain.getEntry", { entryId });
|
|
910
|
-
if (!entry) {
|
|
911
|
-
return {
|
|
912
|
-
text: `Entry \`${entryId}\` not found. Try search to find the right ID.`,
|
|
913
|
-
quality: { score: 0, maxScore: 10, checks: [] }
|
|
914
|
-
};
|
|
915
|
-
}
|
|
916
|
-
const collections = await getCollections();
|
|
917
|
-
const collMap = /* @__PURE__ */ new Map();
|
|
918
|
-
for (const c of collections) collMap.set(c._id, c.slug);
|
|
919
|
-
const collectionSlug = collMap.get(entry.collectionId) ?? "unknown";
|
|
920
|
-
const profile = await getProfile(collectionSlug);
|
|
921
|
-
const relations = await kernelQuery("chain.listEntryRelations", { entryId });
|
|
922
|
-
const linksCreated = [];
|
|
923
|
-
for (const r of relations) {
|
|
924
|
-
const otherId = r.fromId === entry._id ? r.toId : r.fromId;
|
|
925
|
-
linksCreated.push({
|
|
926
|
-
targetEntryId: otherId,
|
|
927
|
-
targetName: "",
|
|
928
|
-
targetCollection: "",
|
|
929
|
-
relationType: r.type
|
|
930
|
-
});
|
|
931
|
-
}
|
|
932
|
-
const descField = profile.descriptionField;
|
|
933
|
-
const description = typeof entry.data?.[descField] === "string" ? entry.data[descField] : "";
|
|
934
|
-
const ctx = {
|
|
935
|
-
collection: collectionSlug,
|
|
936
|
-
name: entry.name,
|
|
937
|
-
description,
|
|
938
|
-
data: entry.data ?? {},
|
|
939
|
-
entryId: entry.entryId ?? "",
|
|
940
|
-
canonicalKey: entry.canonicalKey,
|
|
941
|
-
linksCreated,
|
|
942
|
-
linksSuggested: [],
|
|
943
|
-
collectionFields: []
|
|
944
|
-
};
|
|
945
|
-
const quality = scoreQuality(ctx, profile);
|
|
946
|
-
const lines = [
|
|
947
|
-
`# Quality Check: ${entry.entryId ?? entry.name}`,
|
|
948
|
-
`**${entry.name}** in \`${collectionSlug}\` [${entry.status}]`,
|
|
949
|
-
"",
|
|
950
|
-
formatQualityReport(quality)
|
|
951
|
-
];
|
|
952
|
-
if (quality.score < 10) {
|
|
953
|
-
const failedChecks = quality.checks.filter((c) => !c.passed && c.suggestion);
|
|
954
|
-
if (failedChecks.length > 0) {
|
|
955
|
-
lines.push("");
|
|
956
|
-
lines.push(`_To improve: use \`update-entry\` to fill missing fields, or \`relations action=create\` to add connections._`);
|
|
957
|
-
}
|
|
958
|
-
}
|
|
959
|
-
return { text: lines.join("\n"), quality };
|
|
960
|
-
}
|
|
961
768
|
var AUTO_LINK_CONFIDENCE_THRESHOLD = 35;
|
|
962
769
|
var MAX_AUTO_LINKS = 5;
|
|
963
770
|
var MAX_SUGGESTIONS = 5;
|
|
@@ -1269,7 +1076,8 @@ var captureSuccessOutputSchema = z2.object({
|
|
|
1269
1076
|
// accept it or a refused response fails to validate on `status` (codex review, completing the
|
|
1270
1077
|
// coherencyRefusal contract fix).
|
|
1271
1078
|
status: z2.enum(["draft", "committed", "proposed", "draft_on_failure"]),
|
|
1272
|
-
|
|
1079
|
+
// WP-480 S1: `qualityScore` (required, client N/10) deleted — a named, accepted
|
|
1080
|
+
// breaking output-contract change. The server verdict stays as `qualityVerdict`.
|
|
1273
1081
|
qualityVerdict: z2.record(z2.unknown()).optional(),
|
|
1274
1082
|
classifier: captureClassifierSchema.optional(),
|
|
1275
1083
|
studioUrl: z2.string().optional(),
|
|
@@ -1901,19 +1709,6 @@ Use \`entries action=get\` to inspect the existing entry, or \`update-entry\` to
|
|
|
1901
1709
|
if (advisedLinks.length > 0) {
|
|
1902
1710
|
entryWarnings.push(`${advisedLinks.length} auto-link(s) advised, not written (untypeable) \u2014 accept with \`relations action=create\` to promote.`);
|
|
1903
1711
|
}
|
|
1904
|
-
const captureCtx = {
|
|
1905
|
-
collection: resolvedCollection,
|
|
1906
|
-
name,
|
|
1907
|
-
description,
|
|
1908
|
-
context,
|
|
1909
|
-
data,
|
|
1910
|
-
entryId: finalEntryId,
|
|
1911
|
-
canonicalKey,
|
|
1912
|
-
linksCreated,
|
|
1913
|
-
linksSuggested,
|
|
1914
|
-
collectionFields: col.fields ?? []
|
|
1915
|
-
};
|
|
1916
|
-
const quality = scoreQuality(captureCtx, profile);
|
|
1917
1712
|
const tAfterQuality = Date.now();
|
|
1918
1713
|
const cardinalityWarning = cardinalityCheck?.warning ?? null;
|
|
1919
1714
|
if (contradictionWarnings.length > 0) {
|
|
@@ -2108,13 +1903,6 @@ Use \`entries action=get\` to inspect the existing entry, or \`update-entry\` to
|
|
|
2108
1903
|
lines.push(`_Grounding: ${parts.join(", ")}. Review and link if relevant._`);
|
|
2109
1904
|
}
|
|
2110
1905
|
}
|
|
2111
|
-
lines.push("");
|
|
2112
|
-
lines.push(formatQualityReport(quality));
|
|
2113
|
-
const failedChecks = quality.checks.filter((c) => !c.passed);
|
|
2114
|
-
if (failedChecks.length > 0) {
|
|
2115
|
-
lines.push("");
|
|
2116
|
-
lines.push(`_To improve: \`update-entry entryId="${finalEntryId}"\` to fill missing fields._`);
|
|
2117
|
-
}
|
|
2118
1906
|
const fieldGuidanceSection = formatFieldGuidance(col.fields ?? []);
|
|
2119
1907
|
if (fieldGuidanceSection) {
|
|
2120
1908
|
lines.push("");
|
|
@@ -2194,17 +1982,18 @@ Use \`entries action=get\` to inspect the existing entry, or \`update-entry\` to
|
|
|
2194
1982
|
lines.push("");
|
|
2195
1983
|
lines.push(coachingSection);
|
|
2196
1984
|
}
|
|
1985
|
+
const hasQualityGaps = verdictResult?.verdict != null && verdictResult.verdict.passed === false || formativeHints.length > 0;
|
|
2197
1986
|
lines.push("");
|
|
2198
1987
|
lines.push("## Next Steps");
|
|
2199
1988
|
const eid = finalEntryId || "(check entry ID)";
|
|
2200
1989
|
if (finalStatus === "committed") {
|
|
2201
1990
|
lines.push(`1. **Connect it:** \`graph action=suggest entryId="${eid}"\` \u2014 discover additional links`);
|
|
2202
|
-
if (
|
|
1991
|
+
if (hasQualityGaps) {
|
|
2203
1992
|
lines.push(`2. **Improve quality:** \`update-entry entryId="${eid}"\` \u2014 fill missing fields`);
|
|
2204
1993
|
}
|
|
2205
1994
|
} else if (finalStatus === "proposed") {
|
|
2206
1995
|
lines.push(`1. **Connect it:** \`graph action=suggest entryId="${eid}"\` \u2014 discover additional links to support the proposal`);
|
|
2207
|
-
if (
|
|
1996
|
+
if (hasQualityGaps) {
|
|
2208
1997
|
lines.push(`2. **Improve quality:** \`update-entry entryId="${eid}"\` \u2014 strengthen the entry before approval`);
|
|
2209
1998
|
}
|
|
2210
1999
|
} else {
|
|
@@ -2212,7 +2001,7 @@ Use \`entries action=get\` to inspect the existing entry, or \`update-entry\` to
|
|
|
2212
2001
|
lines.push(`1. **Connect it:** \`graph action=suggest entryId="${eid}"\` \u2014 discover what this should link to`);
|
|
2213
2002
|
}
|
|
2214
2003
|
lines.push(`${userLinkResults.length === 0 ? "2" : "1"}. **Accept it:** \`commit-entry entryId="${eid}"\` \u2014 promote from draft to SSOT on the Chain`);
|
|
2215
|
-
if (
|
|
2004
|
+
if (hasQualityGaps) {
|
|
2216
2005
|
lines.push(`${userLinkResults.length === 0 ? "3" : "2"}. **Improve quality:** \`update-entry entryId="${eid}"\` \u2014 fill missing fields`);
|
|
2217
2006
|
}
|
|
2218
2007
|
}
|
|
@@ -2239,7 +2028,8 @@ Use \`entries action=get\` to inspect the existing entry, or \`update-entry\` to
|
|
|
2239
2028
|
}
|
|
2240
2029
|
next.push({ tool: "commit-entry", description: "Accept onto Chain", parameters: { entryId: finalEntryId } });
|
|
2241
2030
|
}
|
|
2242
|
-
const
|
|
2031
|
+
const qualityPhrase = verdictQualityPhrase(verdictResult);
|
|
2032
|
+
const summary = finalStatus === "committed" ? `Captured and accepted ${finalEntryId} (${name}) onto ${resolvedCollection}. ${qualityPhrase}` : finalStatus === "proposed" ? `Captured ${finalEntryId} (${name}) in ${resolvedCollection} and created a proposal for acceptance. ${qualityPhrase}` : `Captured ${finalEntryId} (${name}) as draft in ${resolvedCollection}. ${qualityPhrase}`;
|
|
2243
2033
|
const expectedFields = (col.fields ?? []).map((f) => ({
|
|
2244
2034
|
key: f.key,
|
|
2245
2035
|
type: f.type,
|
|
@@ -2275,7 +2065,8 @@ Use \`entries action=get\` to inspect the existing entry, or \`update-entry\` to
|
|
|
2275
2065
|
status: finalStatus,
|
|
2276
2066
|
// WP-465 surface parity: thread full refusal (routes + mode) for coherency-refused auto-commits.
|
|
2277
2067
|
...commitRefusal ? { coherencyRefusal: commitRefusal } : {},
|
|
2278
|
-
|
|
2068
|
+
// WP-480 S1: the server verdict rides `qualityVerdict` (named breaking
|
|
2069
|
+
// output-contract change, pre-launch).
|
|
2279
2070
|
qualityVerdict: verdictResult?.verdict ? { ...verdictResult.verdict, source: verdictResult.source ?? "heuristic" } : void 0,
|
|
2280
2071
|
...classifierMeta && { classifier: classifierMeta },
|
|
2281
2072
|
...studioUrl && { studioUrl },
|
|
@@ -3012,6 +2803,16 @@ async function runSemanticConflictPreflight(name, description, collectionHint) {
|
|
|
3012
2803
|
return [];
|
|
3013
2804
|
}
|
|
3014
2805
|
}
|
|
2806
|
+
function verdictQualityPhrase(result) {
|
|
2807
|
+
const v = result?.verdict;
|
|
2808
|
+
const criteria = v?.criteria ?? [];
|
|
2809
|
+
if (!v || criteria.length === 0) {
|
|
2810
|
+
return "Quality: not evaluated (no rubric for this type).";
|
|
2811
|
+
}
|
|
2812
|
+
const total = criteria.length;
|
|
2813
|
+
const passed = criteria.filter((c) => c.passed).length;
|
|
2814
|
+
return v.passed ? `Quality: all ${total} rubric criteria pass.` : `Quality: ${passed}/${total} rubric criteria pass \u2014 see the coaching above.`;
|
|
2815
|
+
}
|
|
3015
2816
|
function formatRubricCoaching(result) {
|
|
3016
2817
|
const { verdict, rogerMartin } = result;
|
|
3017
2818
|
if (!verdict || verdict.criteria.length === 0) return "";
|
|
@@ -5787,8 +5588,10 @@ import { z as z8 } from "zod/v3";
|
|
|
5787
5588
|
var COLLECTIONS_ACTIONS = ["list", "create", "update", "describe", "audit", "export"];
|
|
5788
5589
|
var qualityCriterionSchema = z8.object({
|
|
5789
5590
|
field: z8.string().describe("Entry data field key this criterion applies to, e.g. 'description', 'owner'"),
|
|
5790
|
-
|
|
5791
|
-
|
|
5591
|
+
// WP-480 S1: `max_length` — must mirror the Convex rule union, or the tool rejects a
|
|
5592
|
+
// valid criterion before the request ever reaches the server.
|
|
5593
|
+
rule: z8.enum(["required", "min_length", "max_length", "pattern"]).describe("'required': field must be non-empty (blocks accept). 'min_length': minimum string length (warns). 'max_length': maximum string length (warns). 'pattern': regex match (warns)."),
|
|
5594
|
+
value: z8.string().optional().describe("For min_length/max_length: the length bound as a string integer. For pattern: the regex string. Unused for 'required'.")
|
|
5792
5595
|
});
|
|
5793
5596
|
var fieldSchema = z8.object({
|
|
5794
5597
|
key: z8.string().describe("Field key, e.g. 'description', 'severity', 'status'"),
|
|
@@ -7635,20 +7438,24 @@ import { z as z12 } from "zod/v3";
|
|
|
7635
7438
|
var QUALITY_ACTIONS = ["check", "re-evaluate"];
|
|
7636
7439
|
var qualitySchema = z12.object({
|
|
7637
7440
|
action: z12.enum(QUALITY_ACTIONS).describe(
|
|
7638
|
-
"'check':
|
|
7441
|
+
"'check': read the entry's server quality verdict (tier + criteria). 're-evaluate': trigger fresh evaluation."
|
|
7639
7442
|
),
|
|
7640
7443
|
entryId: z12.string().describe("Entry ID, e.g. 'TEN-graph-db', '<PREFIX>-<n>'"),
|
|
7641
7444
|
context: z12.enum(["capture", "commit", "review"]).default("review").optional().describe("For re-evaluate: evaluation context")
|
|
7642
7445
|
});
|
|
7643
7446
|
var qualityCheckOutputSchema = z12.object({
|
|
7644
7447
|
entryId: z12.string(),
|
|
7645
|
-
|
|
7646
|
-
|
|
7448
|
+
/** WP-480 S1: false = the ID does not resolve to an entry (typo/deleted) — distinct from "no verdict yet". */
|
|
7449
|
+
entryFound: z12.boolean().optional(),
|
|
7450
|
+
hasVerdict: z12.boolean(),
|
|
7451
|
+
/** WP-480 S1: the verdict was judged against content the entry no longer has — re-evaluate for a current one. */
|
|
7452
|
+
stale: z12.boolean().optional(),
|
|
7453
|
+
tier: z12.string().optional(),
|
|
7454
|
+
passed: z12.boolean().optional(),
|
|
7647
7455
|
criteria: z12.array(z12.object({
|
|
7648
|
-
|
|
7649
|
-
|
|
7650
|
-
|
|
7651
|
-
met: z12.boolean()
|
|
7456
|
+
id: z12.string(),
|
|
7457
|
+
passed: z12.boolean(),
|
|
7458
|
+
hint: z12.string().optional()
|
|
7652
7459
|
}))
|
|
7653
7460
|
});
|
|
7654
7461
|
var qualityReevaluateOutputSchema = z12.object({
|
|
@@ -7663,7 +7470,7 @@ function registerQualityTools(server) {
|
|
|
7663
7470
|
"quality",
|
|
7664
7471
|
{
|
|
7665
7472
|
title: "Quality",
|
|
7666
|
-
description: "Assess and re-evaluate entry quality. Two actions:\n\n- **check**:
|
|
7473
|
+
description: "Assess and re-evaluate entry quality. Two actions:\n\n- **check**: Read the entry's server quality verdict (tier + criteria) \u2014 it does not re-score. Suggests links when the entry has no relations.\n- **re-evaluate**: Trigger a fresh quality evaluation. Returns heuristic verdict immediately, schedules LLM evaluation in background.",
|
|
7667
7474
|
inputSchema: qualitySchema,
|
|
7668
7475
|
annotations: { readOnlyHint: false, destructiveHint: false, idempotentHint: true, openWorldHint: false }
|
|
7669
7476
|
},
|
|
@@ -7684,70 +7491,100 @@ function registerQualityTools(server) {
|
|
|
7684
7491
|
);
|
|
7685
7492
|
trackWriteTool(qualityTool);
|
|
7686
7493
|
}
|
|
7687
|
-
async function
|
|
7688
|
-
|
|
7689
|
-
|
|
7690
|
-
|
|
7691
|
-
|
|
7692
|
-
|
|
7693
|
-
|
|
7694
|
-
|
|
7695
|
-
|
|
7696
|
-
|
|
7697
|
-
|
|
7698
|
-
|
|
7699
|
-
|
|
7700
|
-
|
|
7701
|
-
|
|
7702
|
-
|
|
7494
|
+
async function appendLinkSuggestions(entryId, lines) {
|
|
7495
|
+
let relationCount = -1;
|
|
7496
|
+
try {
|
|
7497
|
+
const relations = await kernelQuery("chain.listEntryRelations", { entryId });
|
|
7498
|
+
relationCount = relations?.length ?? 0;
|
|
7499
|
+
} catch {
|
|
7500
|
+
return;
|
|
7501
|
+
}
|
|
7502
|
+
if (relationCount !== 0) return;
|
|
7503
|
+
try {
|
|
7504
|
+
const suggestions = await kernelQuery("chain.graphSuggestLinks", {
|
|
7505
|
+
entryId,
|
|
7506
|
+
maxHops: 2,
|
|
7507
|
+
limit: 3
|
|
7508
|
+
});
|
|
7509
|
+
if ((suggestions?.suggestions?.length ?? 0) > 0) {
|
|
7510
|
+
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");
|
|
7511
|
+
lines.push(`
|
|
7703
7512
|
## Suggested Links to Improve Quality
|
|
7704
|
-
${linkHints}
|
|
7705
|
-
}
|
|
7706
|
-
} catch {
|
|
7513
|
+
${linkHints}`);
|
|
7707
7514
|
}
|
|
7515
|
+
} catch {
|
|
7708
7516
|
}
|
|
7517
|
+
}
|
|
7518
|
+
async function handleCheck(entryId) {
|
|
7519
|
+
const res = await kernelQuery("quality.getLatestVerdictForEntry", { entryId });
|
|
7520
|
+
if (!res || !res.entryFound) {
|
|
7521
|
+
return successResult(
|
|
7522
|
+
`# Quality Check: ${entryId}
|
|
7523
|
+
|
|
7524
|
+
Entry \`${entryId}\` not found. Use \`search\` to find the right ID.`,
|
|
7525
|
+
`Entry ${entryId} not found \u2014 search for the correct ID.`,
|
|
7526
|
+
{ entryId, entryFound: false, hasVerdict: false, criteria: [] },
|
|
7527
|
+
[{ tool: "search", description: "Find the entry", parameters: { query: entryId } }]
|
|
7528
|
+
);
|
|
7529
|
+
}
|
|
7530
|
+
const verdict = res.verdict;
|
|
7531
|
+
if (!verdict) {
|
|
7532
|
+
const lines2 = [
|
|
7533
|
+
`# Quality Check: ${entryId}`,
|
|
7534
|
+
"",
|
|
7535
|
+
`No quality verdict yet for \`${entryId}\`. Run \`quality action=re-evaluate entryId="${entryId}"\` to evaluate it.`
|
|
7536
|
+
];
|
|
7537
|
+
await appendLinkSuggestions(entryId, lines2);
|
|
7538
|
+
return successResult(
|
|
7539
|
+
lines2.join("\n"),
|
|
7540
|
+
`No quality verdict yet for ${entryId} \u2014 re-evaluate to produce one.`,
|
|
7541
|
+
{ entryId, entryFound: true, hasVerdict: false, criteria: [] },
|
|
7542
|
+
[{ tool: "quality", description: "Evaluate now", parameters: { action: "re-evaluate", entryId } }]
|
|
7543
|
+
);
|
|
7544
|
+
}
|
|
7545
|
+
const criteria = verdict.criteria ?? [];
|
|
7546
|
+
const lines = [`# Quality Check: ${entryId}`, ""];
|
|
7547
|
+
if (res.stale) {
|
|
7548
|
+
lines.push(
|
|
7549
|
+
`> STALE \u2014 this verdict was judged against an earlier version of \`${entryId}\`, which has been edited since. Run \`quality action=re-evaluate entryId="${entryId}"\` for a current verdict.`,
|
|
7550
|
+
""
|
|
7551
|
+
);
|
|
7552
|
+
}
|
|
7553
|
+
const verdictSection = formatRubricVerdictSection(verdict);
|
|
7554
|
+
lines.push(verdictSection || `Verdict tier \`${verdict.tier ?? "unknown"}\` for \`${entryId}\` \u2014 no rubric criteria to report.`);
|
|
7555
|
+
await appendLinkSuggestions(entryId, lines);
|
|
7709
7556
|
try {
|
|
7710
|
-
const
|
|
7711
|
-
|
|
7712
|
-
|
|
7713
|
-
|
|
7714
|
-
|
|
7715
|
-
|
|
7716
|
-
|
|
7717
|
-
|
|
7718
|
-
|
|
7719
|
-
|
|
7720
|
-
|
|
7721
|
-
|
|
7722
|
-
llm_duration_ms: verdict.llmDurationMs,
|
|
7723
|
-
llm_error: verdict.llmError,
|
|
7724
|
-
has_roger_martin: !!verdict.rogerMartin
|
|
7725
|
-
});
|
|
7726
|
-
} catch {
|
|
7727
|
-
}
|
|
7728
|
-
}
|
|
7557
|
+
const wsForTracking = await getWorkspaceContext();
|
|
7558
|
+
trackQualityCheck(wsForTracking.workspaceId, {
|
|
7559
|
+
entry_id: entryId,
|
|
7560
|
+
entry_type: verdict.canonicalKey ?? "",
|
|
7561
|
+
tier: verdict.tier ?? "",
|
|
7562
|
+
passed: verdict.passed ?? false,
|
|
7563
|
+
source: verdict.source ?? "",
|
|
7564
|
+
llm_status: verdict.llmStatus,
|
|
7565
|
+
llm_duration_ms: verdict.llmDurationMs,
|
|
7566
|
+
llm_error: verdict.llmError,
|
|
7567
|
+
has_roger_martin: !!verdict.rogerMartin
|
|
7568
|
+
});
|
|
7729
7569
|
} catch {
|
|
7730
7570
|
}
|
|
7731
|
-
|
|
7732
|
-
|
|
7733
|
-
|
|
7734
|
-
|
|
7735
|
-
|
|
7736
|
-
|
|
7737
|
-
|
|
7738
|
-
|
|
7739
|
-
|
|
7740
|
-
|
|
7741
|
-
|
|
7742
|
-
|
|
7743
|
-
|
|
7744
|
-
|
|
7745
|
-
}
|
|
7746
|
-
|
|
7747
|
-
|
|
7748
|
-
]
|
|
7749
|
-
)
|
|
7750
|
-
};
|
|
7571
|
+
const passedCount = criteria.filter((c) => c.passed).length;
|
|
7572
|
+
return successResult(
|
|
7573
|
+
lines.join("\n"),
|
|
7574
|
+
`Quality check for ${entryId}: ${verdict.tier ?? "unknown"} tier, ${passedCount}/${criteria.length} criteria pass.` + (res.stale ? " STALE \u2014 the entry was edited after this verdict; re-evaluate for a current one." : ""),
|
|
7575
|
+
{
|
|
7576
|
+
entryId,
|
|
7577
|
+
entryFound: true,
|
|
7578
|
+
hasVerdict: true,
|
|
7579
|
+
stale: res.stale ?? false,
|
|
7580
|
+
tier: verdict.tier,
|
|
7581
|
+
passed: verdict.passed,
|
|
7582
|
+
criteria: criteria.map((c) => ({ id: c.id, passed: c.passed, hint: c.hint }))
|
|
7583
|
+
},
|
|
7584
|
+
[
|
|
7585
|
+
{ tool: "graph", description: "Suggest connections", parameters: { action: "suggest", entryId } }
|
|
7586
|
+
]
|
|
7587
|
+
);
|
|
7751
7588
|
}
|
|
7752
7589
|
async function handleReEvaluate(entryId, context) {
|
|
7753
7590
|
requireWriteAccess();
|
|
@@ -14813,4 +14650,4 @@ export {
|
|
|
14813
14650
|
createProductBrainServer,
|
|
14814
14651
|
initFeatureFlags
|
|
14815
14652
|
};
|
|
14816
|
-
//# sourceMappingURL=chunk-
|
|
14653
|
+
//# sourceMappingURL=chunk-5PUUKR7Q.js.map
|