@productbrain/mcp 0.0.1-beta.2110 → 0.0.1-beta.2122
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 "";
|
|
@@ -3630,6 +3431,20 @@ function renderWhyLine(e) {
|
|
|
3630
3431
|
}
|
|
3631
3432
|
return { line: null, why, whySourceKey: null };
|
|
3632
3433
|
}
|
|
3434
|
+
function renderVerificationLine(e) {
|
|
3435
|
+
if (!e.verificationStatus) return null;
|
|
3436
|
+
const verifiedBy = typeof e.verifiedBy === "string" ? e.verifiedBy : void 0;
|
|
3437
|
+
const attestation = e.attestation && typeof e.attestation === "object" ? e.attestation : void 0;
|
|
3438
|
+
const strength = attestation && typeof attestation.strength === "string" ? attestation.strength : void 0;
|
|
3439
|
+
const basis = attestation && typeof attestation.basis === "string" ? attestation.basis : void 0;
|
|
3440
|
+
let who;
|
|
3441
|
+
if (basis && basis !== "attested") {
|
|
3442
|
+
who = verifiedBy ? ` (${verifiedBy})` : "";
|
|
3443
|
+
} else {
|
|
3444
|
+
who = verifiedBy ? ` \u2014 by ${verifiedBy}${strength ? ` (${strength})` : ""}` : "";
|
|
3445
|
+
}
|
|
3446
|
+
return `**Verification:** ${String(e.verificationStatus)}${who}`;
|
|
3447
|
+
}
|
|
3633
3448
|
var ENTRIES_ACTIONS = ["list", "get", "batch", "search"];
|
|
3634
3449
|
var entriesSchema = z4.object({
|
|
3635
3450
|
action: z4.enum(ENTRIES_ACTIONS).describe(
|
|
@@ -3656,6 +3471,14 @@ var entriesGetOutputSchema = z4.object({
|
|
|
3656
3471
|
origin: z4.string().optional(),
|
|
3657
3472
|
originDetail: z4.string().optional(),
|
|
3658
3473
|
verificationStatus: z4.string().optional(),
|
|
3474
|
+
// Attestation-model finding (PR #341 review): the server's honest verifier label and
|
|
3475
|
+
// derived attestation strength/basis — connectors NEVER re-derive strength (spec §5),
|
|
3476
|
+
// they only render what chain.getEntry ships. Mirrors packages/cli EntryFromApi.
|
|
3477
|
+
verifiedBy: z4.string().optional(),
|
|
3478
|
+
attestation: z4.object({
|
|
3479
|
+
strength: z4.enum(["human-direct", "delegated", "system", "unattested"]),
|
|
3480
|
+
basis: z4.string().optional()
|
|
3481
|
+
}).optional(),
|
|
3659
3482
|
sourceRef: z4.string().optional(),
|
|
3660
3483
|
sourceExcerpt: z4.string().optional(),
|
|
3661
3484
|
why: z4.string().optional(),
|
|
@@ -3700,6 +3523,13 @@ var entriesBatchOutputSchema = z4.object({
|
|
|
3700
3523
|
origin: z4.string().optional(),
|
|
3701
3524
|
originDetail: z4.string().optional(),
|
|
3702
3525
|
verificationStatus: z4.string().optional(),
|
|
3526
|
+
// Attestation-model finding (PR #341 review): mirror entriesGetOutputSchema — batch
|
|
3527
|
+
// entries now carry the same honest verifiedBy/attestation fields.
|
|
3528
|
+
verifiedBy: z4.string().optional(),
|
|
3529
|
+
attestation: z4.object({
|
|
3530
|
+
strength: z4.enum(["human-direct", "delegated", "system", "unattested"]),
|
|
3531
|
+
basis: z4.string().optional()
|
|
3532
|
+
}).optional(),
|
|
3703
3533
|
sourceRef: z4.string().optional(),
|
|
3704
3534
|
sourceExcerpt: z4.string().optional(),
|
|
3705
3535
|
// TEN-2191: mirror entriesGetOutputSchema — batch entries now carry why/whyQuality.
|
|
@@ -3786,7 +3616,8 @@ async function handleGet(entryId) {
|
|
|
3786
3616
|
const detail = e.originDetail ? ` (${e.originDetail})` : "";
|
|
3787
3617
|
lines.push(`**Origin:** ${e.origin}${detail}`);
|
|
3788
3618
|
}
|
|
3789
|
-
|
|
3619
|
+
const verificationLine = renderVerificationLine(e);
|
|
3620
|
+
if (verificationLine) lines.push(verificationLine);
|
|
3790
3621
|
if (e.sourceRef) lines.push(`**Source ref:** ${e.sourceRef}`);
|
|
3791
3622
|
if (e.sourceExcerpt) lines.push(`**Source excerpt:** ${e.sourceExcerpt}`);
|
|
3792
3623
|
if (epistemic) {
|
|
@@ -3837,6 +3668,11 @@ async function handleGet(entryId) {
|
|
|
3837
3668
|
...e.origin ? { origin: String(e.origin) } : {},
|
|
3838
3669
|
...e.originDetail ? { originDetail: String(e.originDetail) } : {},
|
|
3839
3670
|
...e.verificationStatus ? { verificationStatus: String(e.verificationStatus) } : {},
|
|
3671
|
+
// Attestation-model finding (PR #341 review): forward the server's honest verifier
|
|
3672
|
+
// label + derived attestation strength/basis on the structured payload too — the
|
|
3673
|
+
// agent surface must see the same fields the text render displays (spec §5).
|
|
3674
|
+
...typeof e.verifiedBy === "string" ? { verifiedBy: e.verifiedBy } : {},
|
|
3675
|
+
...e.attestation && typeof e.attestation === "object" ? { attestation: e.attestation } : {},
|
|
3840
3676
|
...e.sourceRef ? { sourceRef: String(e.sourceRef) } : {},
|
|
3841
3677
|
...e.sourceExcerpt ? { sourceExcerpt: String(e.sourceExcerpt) } : {},
|
|
3842
3678
|
// TEN-2191: surface the WHY and its quality in the structured payload too.
|
|
@@ -3897,9 +3733,8 @@ async function handleBatch(entryIds) {
|
|
|
3897
3733
|
const detail = entry.originDetail ? ` (${entry.originDetail})` : "";
|
|
3898
3734
|
lines.push(`**Origin:** ${entry.origin}${detail}`);
|
|
3899
3735
|
}
|
|
3900
|
-
|
|
3901
|
-
|
|
3902
|
-
}
|
|
3736
|
+
const entryVerificationLine = renderVerificationLine(entry);
|
|
3737
|
+
if (entryVerificationLine) lines.push(entryVerificationLine);
|
|
3903
3738
|
if (entry.sourceRef) {
|
|
3904
3739
|
lines.push(`**Source ref:** ${entry.sourceRef}`);
|
|
3905
3740
|
}
|
|
@@ -3955,6 +3790,10 @@ async function handleBatch(entryIds) {
|
|
|
3955
3790
|
...entry.origin ? { origin: String(entry.origin) } : {},
|
|
3956
3791
|
...entry.originDetail ? { originDetail: String(entry.originDetail) } : {},
|
|
3957
3792
|
...entry.verificationStatus ? { verificationStatus: String(entry.verificationStatus) } : {},
|
|
3793
|
+
// Attestation-model finding (PR #341 review): mirror handleGet — forward the
|
|
3794
|
+
// honest verifier label + derived attestation on batch's structured payload too.
|
|
3795
|
+
...typeof entry.verifiedBy === "string" ? { verifiedBy: entry.verifiedBy } : {},
|
|
3796
|
+
...entry.attestation && typeof entry.attestation === "object" ? { attestation: entry.attestation } : {},
|
|
3958
3797
|
...entry.sourceRef ? { sourceRef: String(entry.sourceRef) } : {},
|
|
3959
3798
|
...entry.sourceExcerpt ? { sourceExcerpt: String(entry.sourceExcerpt) } : {},
|
|
3960
3799
|
...entry.workflowStatus ? { workflowStatus: String(entry.workflowStatus) } : {},
|
|
@@ -5787,8 +5626,10 @@ import { z as z8 } from "zod/v3";
|
|
|
5787
5626
|
var COLLECTIONS_ACTIONS = ["list", "create", "update", "describe", "audit", "export"];
|
|
5788
5627
|
var qualityCriterionSchema = z8.object({
|
|
5789
5628
|
field: z8.string().describe("Entry data field key this criterion applies to, e.g. 'description', 'owner'"),
|
|
5790
|
-
|
|
5791
|
-
|
|
5629
|
+
// WP-480 S1: `max_length` — must mirror the Convex rule union, or the tool rejects a
|
|
5630
|
+
// valid criterion before the request ever reaches the server.
|
|
5631
|
+
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)."),
|
|
5632
|
+
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
5633
|
});
|
|
5793
5634
|
var fieldSchema = z8.object({
|
|
5794
5635
|
key: z8.string().describe("Field key, e.g. 'description', 'severity', 'status'"),
|
|
@@ -7635,20 +7476,24 @@ import { z as z12 } from "zod/v3";
|
|
|
7635
7476
|
var QUALITY_ACTIONS = ["check", "re-evaluate"];
|
|
7636
7477
|
var qualitySchema = z12.object({
|
|
7637
7478
|
action: z12.enum(QUALITY_ACTIONS).describe(
|
|
7638
|
-
"'check':
|
|
7479
|
+
"'check': read the entry's server quality verdict (tier + criteria). 're-evaluate': trigger fresh evaluation."
|
|
7639
7480
|
),
|
|
7640
7481
|
entryId: z12.string().describe("Entry ID, e.g. 'TEN-graph-db', '<PREFIX>-<n>'"),
|
|
7641
7482
|
context: z12.enum(["capture", "commit", "review"]).default("review").optional().describe("For re-evaluate: evaluation context")
|
|
7642
7483
|
});
|
|
7643
7484
|
var qualityCheckOutputSchema = z12.object({
|
|
7644
7485
|
entryId: z12.string(),
|
|
7645
|
-
|
|
7646
|
-
|
|
7486
|
+
/** WP-480 S1: false = the ID does not resolve to an entry (typo/deleted) — distinct from "no verdict yet". */
|
|
7487
|
+
entryFound: z12.boolean().optional(),
|
|
7488
|
+
hasVerdict: z12.boolean(),
|
|
7489
|
+
/** WP-480 S1: the verdict was judged against content the entry no longer has — re-evaluate for a current one. */
|
|
7490
|
+
stale: z12.boolean().optional(),
|
|
7491
|
+
tier: z12.string().optional(),
|
|
7492
|
+
passed: z12.boolean().optional(),
|
|
7647
7493
|
criteria: z12.array(z12.object({
|
|
7648
|
-
|
|
7649
|
-
|
|
7650
|
-
|
|
7651
|
-
met: z12.boolean()
|
|
7494
|
+
id: z12.string(),
|
|
7495
|
+
passed: z12.boolean(),
|
|
7496
|
+
hint: z12.string().optional()
|
|
7652
7497
|
}))
|
|
7653
7498
|
});
|
|
7654
7499
|
var qualityReevaluateOutputSchema = z12.object({
|
|
@@ -7663,7 +7508,7 @@ function registerQualityTools(server) {
|
|
|
7663
7508
|
"quality",
|
|
7664
7509
|
{
|
|
7665
7510
|
title: "Quality",
|
|
7666
|
-
description: "Assess and re-evaluate entry quality. Two actions:\n\n- **check**:
|
|
7511
|
+
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
7512
|
inputSchema: qualitySchema,
|
|
7668
7513
|
annotations: { readOnlyHint: false, destructiveHint: false, idempotentHint: true, openWorldHint: false }
|
|
7669
7514
|
},
|
|
@@ -7684,70 +7529,100 @@ function registerQualityTools(server) {
|
|
|
7684
7529
|
);
|
|
7685
7530
|
trackWriteTool(qualityTool);
|
|
7686
7531
|
}
|
|
7687
|
-
async function
|
|
7688
|
-
|
|
7689
|
-
|
|
7690
|
-
|
|
7691
|
-
|
|
7692
|
-
|
|
7693
|
-
|
|
7694
|
-
|
|
7695
|
-
|
|
7696
|
-
|
|
7697
|
-
|
|
7698
|
-
|
|
7699
|
-
|
|
7700
|
-
|
|
7701
|
-
|
|
7702
|
-
|
|
7532
|
+
async function appendLinkSuggestions(entryId, lines) {
|
|
7533
|
+
let relationCount = -1;
|
|
7534
|
+
try {
|
|
7535
|
+
const relations = await kernelQuery("chain.listEntryRelations", { entryId });
|
|
7536
|
+
relationCount = relations?.length ?? 0;
|
|
7537
|
+
} catch {
|
|
7538
|
+
return;
|
|
7539
|
+
}
|
|
7540
|
+
if (relationCount !== 0) return;
|
|
7541
|
+
try {
|
|
7542
|
+
const suggestions = await kernelQuery("chain.graphSuggestLinks", {
|
|
7543
|
+
entryId,
|
|
7544
|
+
maxHops: 2,
|
|
7545
|
+
limit: 3
|
|
7546
|
+
});
|
|
7547
|
+
if ((suggestions?.suggestions?.length ?? 0) > 0) {
|
|
7548
|
+
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");
|
|
7549
|
+
lines.push(`
|
|
7703
7550
|
## Suggested Links to Improve Quality
|
|
7704
|
-
${linkHints}
|
|
7705
|
-
}
|
|
7706
|
-
} catch {
|
|
7551
|
+
${linkHints}`);
|
|
7707
7552
|
}
|
|
7553
|
+
} catch {
|
|
7708
7554
|
}
|
|
7555
|
+
}
|
|
7556
|
+
async function handleCheck(entryId) {
|
|
7557
|
+
const res = await kernelQuery("quality.getLatestVerdictForEntry", { entryId });
|
|
7558
|
+
if (!res || !res.entryFound) {
|
|
7559
|
+
return successResult(
|
|
7560
|
+
`# Quality Check: ${entryId}
|
|
7561
|
+
|
|
7562
|
+
Entry \`${entryId}\` not found. Use \`search\` to find the right ID.`,
|
|
7563
|
+
`Entry ${entryId} not found \u2014 search for the correct ID.`,
|
|
7564
|
+
{ entryId, entryFound: false, hasVerdict: false, criteria: [] },
|
|
7565
|
+
[{ tool: "search", description: "Find the entry", parameters: { query: entryId } }]
|
|
7566
|
+
);
|
|
7567
|
+
}
|
|
7568
|
+
const verdict = res.verdict;
|
|
7569
|
+
if (!verdict) {
|
|
7570
|
+
const lines2 = [
|
|
7571
|
+
`# Quality Check: ${entryId}`,
|
|
7572
|
+
"",
|
|
7573
|
+
`No quality verdict yet for \`${entryId}\`. Run \`quality action=re-evaluate entryId="${entryId}"\` to evaluate it.`
|
|
7574
|
+
];
|
|
7575
|
+
await appendLinkSuggestions(entryId, lines2);
|
|
7576
|
+
return successResult(
|
|
7577
|
+
lines2.join("\n"),
|
|
7578
|
+
`No quality verdict yet for ${entryId} \u2014 re-evaluate to produce one.`,
|
|
7579
|
+
{ entryId, entryFound: true, hasVerdict: false, criteria: [] },
|
|
7580
|
+
[{ tool: "quality", description: "Evaluate now", parameters: { action: "re-evaluate", entryId } }]
|
|
7581
|
+
);
|
|
7582
|
+
}
|
|
7583
|
+
const criteria = verdict.criteria ?? [];
|
|
7584
|
+
const lines = [`# Quality Check: ${entryId}`, ""];
|
|
7585
|
+
if (res.stale) {
|
|
7586
|
+
lines.push(
|
|
7587
|
+
`> 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.`,
|
|
7588
|
+
""
|
|
7589
|
+
);
|
|
7590
|
+
}
|
|
7591
|
+
const verdictSection = formatRubricVerdictSection(verdict);
|
|
7592
|
+
lines.push(verdictSection || `Verdict tier \`${verdict.tier ?? "unknown"}\` for \`${entryId}\` \u2014 no rubric criteria to report.`);
|
|
7593
|
+
await appendLinkSuggestions(entryId, lines);
|
|
7709
7594
|
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
|
-
}
|
|
7595
|
+
const wsForTracking = await getWorkspaceContext();
|
|
7596
|
+
trackQualityCheck(wsForTracking.workspaceId, {
|
|
7597
|
+
entry_id: entryId,
|
|
7598
|
+
entry_type: verdict.canonicalKey ?? "",
|
|
7599
|
+
tier: verdict.tier ?? "",
|
|
7600
|
+
passed: verdict.passed ?? false,
|
|
7601
|
+
source: verdict.source ?? "",
|
|
7602
|
+
llm_status: verdict.llmStatus,
|
|
7603
|
+
llm_duration_ms: verdict.llmDurationMs,
|
|
7604
|
+
llm_error: verdict.llmError,
|
|
7605
|
+
has_roger_martin: !!verdict.rogerMartin
|
|
7606
|
+
});
|
|
7729
7607
|
} catch {
|
|
7730
7608
|
}
|
|
7731
|
-
|
|
7732
|
-
|
|
7733
|
-
|
|
7734
|
-
|
|
7735
|
-
|
|
7736
|
-
|
|
7737
|
-
|
|
7738
|
-
|
|
7739
|
-
|
|
7740
|
-
|
|
7741
|
-
|
|
7742
|
-
|
|
7743
|
-
|
|
7744
|
-
|
|
7745
|
-
}
|
|
7746
|
-
|
|
7747
|
-
|
|
7748
|
-
]
|
|
7749
|
-
)
|
|
7750
|
-
};
|
|
7609
|
+
const passedCount = criteria.filter((c) => c.passed).length;
|
|
7610
|
+
return successResult(
|
|
7611
|
+
lines.join("\n"),
|
|
7612
|
+
`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." : ""),
|
|
7613
|
+
{
|
|
7614
|
+
entryId,
|
|
7615
|
+
entryFound: true,
|
|
7616
|
+
hasVerdict: true,
|
|
7617
|
+
stale: res.stale ?? false,
|
|
7618
|
+
tier: verdict.tier,
|
|
7619
|
+
passed: verdict.passed,
|
|
7620
|
+
criteria: criteria.map((c) => ({ id: c.id, passed: c.passed, hint: c.hint }))
|
|
7621
|
+
},
|
|
7622
|
+
[
|
|
7623
|
+
{ tool: "graph", description: "Suggest connections", parameters: { action: "suggest", entryId } }
|
|
7624
|
+
]
|
|
7625
|
+
);
|
|
7751
7626
|
}
|
|
7752
7627
|
async function handleReEvaluate(entryId, context) {
|
|
7753
7628
|
requireWriteAccess();
|
|
@@ -10088,8 +9963,19 @@ function registerVerifyTools(server) {
|
|
|
10088
9963
|
annotations: { readOnlyHint: false, destructiveHint: false, openWorldHint: false }
|
|
10089
9964
|
},
|
|
10090
9965
|
thinWrapper(async ({ entryId }) => {
|
|
10091
|
-
const
|
|
10092
|
-
|
|
9966
|
+
const agentSessionId = getAgentSessionId();
|
|
9967
|
+
const result = await kernelMutation(
|
|
9968
|
+
"chain.verifyEntry",
|
|
9969
|
+
{
|
|
9970
|
+
entryId,
|
|
9971
|
+
...agentSessionId ? { sessionId: agentSessionId } : {}
|
|
9972
|
+
}
|
|
9973
|
+
);
|
|
9974
|
+
const basis = result.attestation?.basis;
|
|
9975
|
+
const strength = result.attestation?.strength;
|
|
9976
|
+
const verifiedBySuffix = result.verifiedBy ? basis && basis !== "attested" ? ` (${result.verifiedBy})` : ` \u2014 by ${result.verifiedBy}${strength ? ` (${strength})` : ""}` : "";
|
|
9977
|
+
const message = result.alreadyVerified ? `Entry ${entryId} is already verified${verifiedBySuffix}.` : `Entry ${entryId} verified${verifiedBySuffix}.`;
|
|
9978
|
+
return successResult(message, message, result);
|
|
10093
9979
|
})
|
|
10094
9980
|
);
|
|
10095
9981
|
trackWriteTool(verifyEntryTool);
|
|
@@ -14813,4 +14699,4 @@ export {
|
|
|
14813
14699
|
createProductBrainServer,
|
|
14814
14700
|
initFeatureFlags
|
|
14815
14701
|
};
|
|
14816
|
-
//# sourceMappingURL=chunk-
|
|
14702
|
+
//# sourceMappingURL=chunk-3CONR5XB.js.map
|