@productbrain/mcp 0.0.1-beta.66 → 0.0.1-beta.67
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/{chunk-UWILSX73.js → chunk-TH5AUVVM.js} +310 -148
- package/dist/chunk-TH5AUVVM.js.map +1 -0
- package/dist/{chunk-4IQ7A5R4.js → chunk-YARRUQBW.js} +103 -37
- package/dist/chunk-YARRUQBW.js.map +1 -0
- package/dist/http.js +2 -2
- package/dist/index.js +2 -2
- package/dist/{smart-capture-EGTUM4XP.js → smart-capture-Q64ZXK65.js} +2 -2
- package/package.json +1 -1
- package/dist/chunk-4IQ7A5R4.js.map +0 -1
- package/dist/chunk-UWILSX73.js.map +0 -1
- /package/dist/{smart-capture-EGTUM4XP.js.map → smart-capture-Q64ZXK65.js.map} +0 -0
|
@@ -939,17 +939,108 @@ function classifyCollection(name, description) {
|
|
|
939
939
|
}))
|
|
940
940
|
};
|
|
941
941
|
}
|
|
942
|
-
function shouldAutoRouteClassification(result) {
|
|
943
|
-
if (result.confidence < CLASSIFIER_AUTO_ROUTE_THRESHOLD) return false;
|
|
944
|
-
if (result.scoreMargin < CLASSIFIER_AMBIGUITY_MARGIN) return false;
|
|
945
|
-
return true;
|
|
946
|
-
}
|
|
947
942
|
function isClassificationAmbiguous(result) {
|
|
948
943
|
return result.scoreMargin < CLASSIFIER_AMBIGUITY_MARGIN;
|
|
949
944
|
}
|
|
950
945
|
|
|
951
|
-
// src/
|
|
952
|
-
var
|
|
946
|
+
// src/lib/resolveCollection.ts
|
|
947
|
+
var HIGH_THRESHOLD = 80;
|
|
948
|
+
var MEDIUM_THRESHOLD = 50;
|
|
949
|
+
function getTier(confidence) {
|
|
950
|
+
if (confidence >= HIGH_THRESHOLD) return "high";
|
|
951
|
+
if (confidence >= MEDIUM_THRESHOLD) return "medium";
|
|
952
|
+
return "low";
|
|
953
|
+
}
|
|
954
|
+
var TYPE_HINT_COLLECTION_MAP = {
|
|
955
|
+
element: "features",
|
|
956
|
+
risk: "tensions",
|
|
957
|
+
decision: "decisions"
|
|
958
|
+
};
|
|
959
|
+
async function resolveCollection(params) {
|
|
960
|
+
const { name, description, typeHint } = params;
|
|
961
|
+
const llmResult = await tryLlmClassifier(name, description);
|
|
962
|
+
if (llmResult) {
|
|
963
|
+
return typeHint ? applyTypeHintBias(llmResult, typeHint) : llmResult;
|
|
964
|
+
}
|
|
965
|
+
const heuristic = classifyCollection(name, description);
|
|
966
|
+
if (heuristic) {
|
|
967
|
+
const resolved = heuristicToResolved(heuristic);
|
|
968
|
+
return typeHint ? applyTypeHintBias(resolved, typeHint) : resolved;
|
|
969
|
+
}
|
|
970
|
+
if (typeHint) {
|
|
971
|
+
const mapped = TYPE_HINT_COLLECTION_MAP[typeHint];
|
|
972
|
+
if (mapped) {
|
|
973
|
+
return {
|
|
974
|
+
collection: mapped,
|
|
975
|
+
confidence: 60,
|
|
976
|
+
tier: "medium",
|
|
977
|
+
alternatives: [],
|
|
978
|
+
classifiedBy: "heuristic",
|
|
979
|
+
reasoning: `Fallback from type hint: ${typeHint} \u2192 ${mapped}`
|
|
980
|
+
};
|
|
981
|
+
}
|
|
982
|
+
}
|
|
983
|
+
return null;
|
|
984
|
+
}
|
|
985
|
+
async function tryLlmClassifier(name, description) {
|
|
986
|
+
try {
|
|
987
|
+
const sessionId = getAgentSessionId();
|
|
988
|
+
const result = await mcpCall(
|
|
989
|
+
"chain.classifyCollection",
|
|
990
|
+
{
|
|
991
|
+
entryName: name,
|
|
992
|
+
entryDescription: description,
|
|
993
|
+
...sessionId ? { agentSessionId: sessionId } : {}
|
|
994
|
+
}
|
|
995
|
+
);
|
|
996
|
+
if (!result || typeof result.collection !== "string" || typeof result.confidence !== "number") {
|
|
997
|
+
return null;
|
|
998
|
+
}
|
|
999
|
+
return {
|
|
1000
|
+
collection: result.collection,
|
|
1001
|
+
confidence: result.confidence,
|
|
1002
|
+
tier: getTier(result.confidence),
|
|
1003
|
+
alternatives: Array.isArray(result.alternatives) ? result.alternatives : [],
|
|
1004
|
+
classifiedBy: "llm",
|
|
1005
|
+
reasoning: typeof result.reasoning === "string" ? result.reasoning : ""
|
|
1006
|
+
};
|
|
1007
|
+
} catch (err) {
|
|
1008
|
+
process.stderr.write(
|
|
1009
|
+
`[resolveCollection] LLM classifier failed, falling back to heuristic: ${err instanceof Error ? err.message : String(err)}
|
|
1010
|
+
`
|
|
1011
|
+
);
|
|
1012
|
+
return null;
|
|
1013
|
+
}
|
|
1014
|
+
}
|
|
1015
|
+
function applyTypeHintBias(result, typeHint) {
|
|
1016
|
+
const hintedCollection = TYPE_HINT_COLLECTION_MAP[typeHint];
|
|
1017
|
+
if (!hintedCollection) return result;
|
|
1018
|
+
if (result.collection === hintedCollection) {
|
|
1019
|
+
const boosted = Math.min(99, result.confidence + 10);
|
|
1020
|
+
return { ...result, confidence: boosted, tier: getTier(boosted) };
|
|
1021
|
+
}
|
|
1022
|
+
if (result.tier === "high") return result;
|
|
1023
|
+
return {
|
|
1024
|
+
...result,
|
|
1025
|
+
collection: hintedCollection,
|
|
1026
|
+
confidence: 65,
|
|
1027
|
+
tier: "medium",
|
|
1028
|
+
reasoning: `Type hint '${typeHint}' overrode ${result.classifiedBy} classification (${result.collection} at ${result.confidence}%)`
|
|
1029
|
+
};
|
|
1030
|
+
}
|
|
1031
|
+
function heuristicToResolved(result) {
|
|
1032
|
+
return {
|
|
1033
|
+
collection: result.collection,
|
|
1034
|
+
confidence: result.confidence,
|
|
1035
|
+
tier: getTier(result.confidence),
|
|
1036
|
+
alternatives: result.candidates.slice(0, 3).map((c) => ({
|
|
1037
|
+
collection: c.collection,
|
|
1038
|
+
confidence: c.confidence
|
|
1039
|
+
})),
|
|
1040
|
+
classifiedBy: "heuristic",
|
|
1041
|
+
reasoning: result.reasons.join("; ")
|
|
1042
|
+
};
|
|
1043
|
+
}
|
|
953
1044
|
|
|
954
1045
|
// src/envelope.ts
|
|
955
1046
|
import { z } from "zod";
|
|
@@ -1159,6 +1250,9 @@ function withEnvelope(handler) {
|
|
|
1159
1250
|
};
|
|
1160
1251
|
}
|
|
1161
1252
|
|
|
1253
|
+
// src/tools/smart-capture-routing.ts
|
|
1254
|
+
var STARTER_COLLECTIONS = CLASSIFIABLE_COLLECTIONS;
|
|
1255
|
+
|
|
1162
1256
|
// src/tools/smart-capture.ts
|
|
1163
1257
|
var AREA_KEYWORDS = {
|
|
1164
1258
|
"Architecture": ["convex", "schema", "database", "migration", "api", "backend", "infrastructure", "scaling", "performance"],
|
|
@@ -1838,7 +1932,7 @@ var captureSchema = z2.object({
|
|
|
1838
1932
|
});
|
|
1839
1933
|
var batchCaptureSchema = z2.object({
|
|
1840
1934
|
entries: z2.array(z2.object({
|
|
1841
|
-
collection: z2.string().describe("Collection slug"),
|
|
1935
|
+
collection: z2.string().optional().describe("Collection slug. Optional \u2014 auto-classified via LLM when omitted (FEAT-160)."),
|
|
1842
1936
|
name: z2.string().describe("Display name"),
|
|
1843
1937
|
description: z2.string().describe("Full context / definition"),
|
|
1844
1938
|
entryId: z2.string().optional().describe("Optional custom entry ID")
|
|
@@ -1857,13 +1951,16 @@ var captureClassifierSchema = z2.object({
|
|
|
1857
1951
|
reasons: z2.array(z2.string()),
|
|
1858
1952
|
candidates: z2.array(
|
|
1859
1953
|
z2.object({
|
|
1860
|
-
collection: z2.
|
|
1861
|
-
signalScore: z2.number(),
|
|
1954
|
+
collection: z2.string(),
|
|
1955
|
+
signalScore: z2.number().optional(),
|
|
1862
1956
|
confidence: z2.number()
|
|
1863
1957
|
})
|
|
1864
1958
|
),
|
|
1865
1959
|
agentProvidedCollection: z2.string().optional(),
|
|
1866
|
-
overrideCommand: z2.string().optional()
|
|
1960
|
+
overrideCommand: z2.string().optional(),
|
|
1961
|
+
classifiedBy: z2.enum(["llm", "heuristic", "explicit"]).optional(),
|
|
1962
|
+
confidenceTier: z2.enum(["high", "medium", "low"]).optional(),
|
|
1963
|
+
reasoning: z2.string().optional()
|
|
1867
1964
|
});
|
|
1868
1965
|
function trackClassifierTelemetry(params) {
|
|
1869
1966
|
const telemetry = {
|
|
@@ -1909,58 +2006,28 @@ function buildClassifierUnknownResult() {
|
|
|
1909
2006
|
)
|
|
1910
2007
|
};
|
|
1911
2008
|
}
|
|
1912
|
-
function
|
|
1913
|
-
|
|
1914
|
-
}
|
|
1915
|
-
|
|
1916
|
-
|
|
1917
|
-
const
|
|
1918
|
-
|
|
1919
|
-
Predicted collection \`${classified.collection}\` is not provisioned/supported for auto-routing in this workspace.
|
|
1920
|
-
Reason: ${classified.reasons.join("; ") || "low signal"}
|
|
1921
|
-
|
|
1922
|
-
Choose one of these provisioned starter collections and retry with \`collection\`:
|
|
1923
|
-
${suggestions}
|
|
2009
|
+
function buildLowConfidenceResult(resolved, classifierMeta) {
|
|
2010
|
+
const allCandidates = [
|
|
2011
|
+
{ collection: resolved.collection, confidence: resolved.confidence },
|
|
2012
|
+
...resolved.alternatives
|
|
2013
|
+
];
|
|
2014
|
+
const suggestions = allCandidates.map((c) => `- \`${c.collection}\` (${c.confidence}% confidence)`).join("\n");
|
|
2015
|
+
const textBody = `Low confidence classification (${resolved.confidence}%, classified by ${resolved.classifiedBy}).
|
|
1924
2016
|
|
|
1925
|
-
|
|
1926
|
-
|
|
1927
|
-
content: [{ type: "text", text: textBody }],
|
|
1928
|
-
structuredContent: failure(
|
|
1929
|
-
"VALIDATION_ERROR",
|
|
1930
|
-
`Collection '${classified.collection}' is not provisioned for auto-routing.`,
|
|
1931
|
-
"Rerun with explicit collection.",
|
|
1932
|
-
[{ tool: "collections", description: "List available collections", parameters: { action: "list" } }],
|
|
1933
|
-
{
|
|
1934
|
-
classifier: {
|
|
1935
|
-
enabled: true,
|
|
1936
|
-
autoRouted: false,
|
|
1937
|
-
agrees: false,
|
|
1938
|
-
abstained: false,
|
|
1939
|
-
topConfidence: classified.topConfidence,
|
|
1940
|
-
confidence: classified.confidence,
|
|
1941
|
-
reasons: classified.reasons,
|
|
1942
|
-
candidates: provisionedCandidates.map((c) => ({ collection: c.collection, signalScore: c.signalScore, confidence: c.confidence }))
|
|
1943
|
-
}
|
|
1944
|
-
}
|
|
1945
|
-
)
|
|
1946
|
-
};
|
|
1947
|
-
}
|
|
1948
|
-
function buildAmbiguousRouteResult(classified, classifierMeta, ambiguousRoute) {
|
|
1949
|
-
const suggestions = buildProvisionedCollectionSuggestions(classifierMeta.candidates);
|
|
1950
|
-
const textBody = "Collection inference is not safe to auto-route yet.\n\n" + (ambiguousRoute ? "Routing held because intent is ambiguous across top candidates.\n\n" : "") + `Predicted: \`${classified.collection}\` (${classified.topConfidence}% top confidence)
|
|
1951
|
-
Reason: ${classified.reasons.join("; ") || "low signal"}
|
|
2017
|
+
Predicted: \`${resolved.collection}\`
|
|
2018
|
+
Reason: ${resolved.reasoning || "low signal"}
|
|
1952
2019
|
|
|
1953
2020
|
Choose one of these and retry with \`collection\`:
|
|
1954
2021
|
${suggestions}
|
|
1955
2022
|
|
|
1956
|
-
Correction path:
|
|
2023
|
+
Correction path: rerun with your chosen \`collection\`, or capture and use \`entries action=move\` later.`;
|
|
1957
2024
|
return {
|
|
1958
2025
|
content: [{ type: "text", text: textBody }],
|
|
1959
2026
|
structuredContent: failure(
|
|
1960
2027
|
"VALIDATION_ERROR",
|
|
1961
|
-
|
|
2028
|
+
`Low confidence routing to '${resolved.collection}' (${resolved.confidence}%, ${resolved.classifiedBy}).`,
|
|
1962
2029
|
"Rerun with explicit collection.",
|
|
1963
|
-
|
|
2030
|
+
allCandidates.map((c) => ({
|
|
1964
2031
|
tool: "capture",
|
|
1965
2032
|
description: `Capture to ${c.collection}`,
|
|
1966
2033
|
parameters: { collection: c.collection }
|
|
@@ -1969,17 +2036,24 @@ Correction path: if this was close, rerun with your chosen \`collection\`.`;
|
|
|
1969
2036
|
)
|
|
1970
2037
|
};
|
|
1971
2038
|
}
|
|
1972
|
-
|
|
1973
|
-
|
|
1974
|
-
|
|
1975
|
-
|
|
1976
|
-
|
|
1977
|
-
|
|
1978
|
-
|
|
1979
|
-
|
|
1980
|
-
|
|
1981
|
-
|
|
1982
|
-
|
|
2039
|
+
function buildClassifierMeta(resolved, overrides = {}) {
|
|
2040
|
+
return {
|
|
2041
|
+
enabled: true,
|
|
2042
|
+
autoRouted: false,
|
|
2043
|
+
agrees: true,
|
|
2044
|
+
abstained: false,
|
|
2045
|
+
topConfidence: resolved.confidence,
|
|
2046
|
+
confidence: resolved.confidence,
|
|
2047
|
+
reasons: resolved.reasoning ? [resolved.reasoning] : [],
|
|
2048
|
+
candidates: [
|
|
2049
|
+
{ collection: resolved.collection, confidence: resolved.confidence },
|
|
2050
|
+
...resolved.alternatives.map((a) => ({ collection: a.collection, confidence: a.confidence }))
|
|
2051
|
+
].slice(0, 3),
|
|
2052
|
+
classifiedBy: resolved.classifiedBy,
|
|
2053
|
+
confidenceTier: resolved.tier,
|
|
2054
|
+
reasoning: resolved.reasoning,
|
|
2055
|
+
...overrides
|
|
2056
|
+
};
|
|
1983
2057
|
}
|
|
1984
2058
|
async function resolveCaptureCollection(params) {
|
|
1985
2059
|
const {
|
|
@@ -1987,33 +2061,26 @@ async function resolveCaptureCollection(params) {
|
|
|
1987
2061
|
name,
|
|
1988
2062
|
description,
|
|
1989
2063
|
classifierFlagOn,
|
|
1990
|
-
supportedCollections,
|
|
1991
2064
|
workspaceId,
|
|
1992
2065
|
explicitCollectionProvided
|
|
1993
2066
|
} = params;
|
|
1994
2067
|
if (collection) {
|
|
1995
|
-
const
|
|
1996
|
-
if (
|
|
1997
|
-
const agrees =
|
|
1998
|
-
const classifierMeta2 = {
|
|
1999
|
-
enabled: true,
|
|
2068
|
+
const resolved2 = await resolveCollection({ name, description });
|
|
2069
|
+
if (resolved2) {
|
|
2070
|
+
const agrees = resolved2.collection === collection;
|
|
2071
|
+
const classifierMeta2 = buildClassifierMeta(resolved2, {
|
|
2000
2072
|
autoRouted: false,
|
|
2001
2073
|
agrees,
|
|
2002
|
-
abstained: false,
|
|
2003
|
-
topConfidence: classified2.topConfidence,
|
|
2004
|
-
confidence: classified2.confidence,
|
|
2005
|
-
reasons: classified2.reasons,
|
|
2006
|
-
candidates: classified2.candidates.slice(0, 3),
|
|
2007
2074
|
agentProvidedCollection: collection,
|
|
2008
2075
|
...!agrees && {
|
|
2009
|
-
overrideCommand: `capture collection="${
|
|
2076
|
+
overrideCommand: `capture collection="${resolved2.collection}"`
|
|
2010
2077
|
}
|
|
2011
|
-
};
|
|
2078
|
+
});
|
|
2012
2079
|
if (!agrees) {
|
|
2013
2080
|
trackClassifierTelemetry({
|
|
2014
2081
|
workspaceId,
|
|
2015
|
-
predictedCollection:
|
|
2016
|
-
confidence:
|
|
2082
|
+
predictedCollection: resolved2.collection,
|
|
2083
|
+
confidence: resolved2.confidence,
|
|
2017
2084
|
autoRouted: false,
|
|
2018
2085
|
reasonCategory: "low-confidence",
|
|
2019
2086
|
explicitCollectionProvided: true,
|
|
@@ -2040,8 +2107,8 @@ async function resolveCaptureCollection(params) {
|
|
|
2040
2107
|
if (!classifierFlagOn) {
|
|
2041
2108
|
return { earlyResult: buildCollectionRequiredResult() };
|
|
2042
2109
|
}
|
|
2043
|
-
const
|
|
2044
|
-
if (!
|
|
2110
|
+
const resolved = await resolveCollection({ name, description });
|
|
2111
|
+
if (!resolved) {
|
|
2045
2112
|
trackClassifierTelemetry({
|
|
2046
2113
|
workspaceId,
|
|
2047
2114
|
predictedCollection: "unknown",
|
|
@@ -2053,59 +2120,34 @@ async function resolveCaptureCollection(params) {
|
|
|
2053
2120
|
});
|
|
2054
2121
|
return { earlyResult: buildClassifierUnknownResult() };
|
|
2055
2122
|
}
|
|
2056
|
-
const
|
|
2057
|
-
|
|
2058
|
-
trackClassifierTelemetry({
|
|
2059
|
-
workspaceId,
|
|
2060
|
-
predictedCollection: classified.collection,
|
|
2061
|
-
confidence: classified.confidence,
|
|
2062
|
-
autoRouted: false,
|
|
2063
|
-
reasonCategory: "non-provisioned",
|
|
2064
|
-
explicitCollectionProvided,
|
|
2065
|
-
outcome: "fallback"
|
|
2066
|
-
});
|
|
2067
|
-
return {
|
|
2068
|
-
earlyResult: buildUnsupportedProvisioningResult(classified, provisionedCandidates)
|
|
2069
|
-
};
|
|
2070
|
-
}
|
|
2071
|
-
const autoRoute = shouldAutoRouteClassification(classified);
|
|
2072
|
-
const ambiguousRoute = isClassificationAmbiguous(classified);
|
|
2073
|
-
const classifierMeta = {
|
|
2074
|
-
enabled: true,
|
|
2075
|
-
autoRouted: autoRoute,
|
|
2076
|
-
agrees: true,
|
|
2077
|
-
abstained: false,
|
|
2078
|
-
topConfidence: classified.topConfidence,
|
|
2079
|
-
confidence: classified.confidence,
|
|
2080
|
-
reasons: classified.reasons,
|
|
2081
|
-
candidates: provisionedCandidates
|
|
2082
|
-
};
|
|
2123
|
+
const autoRoute = resolved.tier !== "low";
|
|
2124
|
+
const classifierMeta = buildClassifierMeta(resolved, { autoRouted: autoRoute });
|
|
2083
2125
|
if (!autoRoute) {
|
|
2084
2126
|
trackClassifierTelemetry({
|
|
2085
2127
|
workspaceId,
|
|
2086
|
-
predictedCollection:
|
|
2087
|
-
confidence:
|
|
2128
|
+
predictedCollection: resolved.collection,
|
|
2129
|
+
confidence: resolved.confidence,
|
|
2088
2130
|
autoRouted: false,
|
|
2089
|
-
reasonCategory:
|
|
2131
|
+
reasonCategory: "low-confidence",
|
|
2090
2132
|
explicitCollectionProvided,
|
|
2091
2133
|
outcome: "fallback"
|
|
2092
2134
|
});
|
|
2093
2135
|
return {
|
|
2094
2136
|
classifierMeta,
|
|
2095
|
-
earlyResult:
|
|
2137
|
+
earlyResult: buildLowConfidenceResult(resolved, classifierMeta)
|
|
2096
2138
|
};
|
|
2097
2139
|
}
|
|
2098
2140
|
trackClassifierTelemetry({
|
|
2099
2141
|
workspaceId,
|
|
2100
|
-
predictedCollection:
|
|
2101
|
-
confidence:
|
|
2142
|
+
predictedCollection: resolved.collection,
|
|
2143
|
+
confidence: resolved.confidence,
|
|
2102
2144
|
autoRouted: true,
|
|
2103
2145
|
reasonCategory: "auto-routed",
|
|
2104
2146
|
explicitCollectionProvided,
|
|
2105
2147
|
outcome: "auto-routed"
|
|
2106
2148
|
});
|
|
2107
2149
|
return {
|
|
2108
|
-
resolvedCollection:
|
|
2150
|
+
resolvedCollection: resolved.collection,
|
|
2109
2151
|
classifierMeta
|
|
2110
2152
|
};
|
|
2111
2153
|
}
|
|
@@ -2131,14 +2173,28 @@ var batchCaptureOutputSchema = z2.object({
|
|
|
2131
2173
|
entryId: z2.string(),
|
|
2132
2174
|
collection: z2.string(),
|
|
2133
2175
|
name: z2.string(),
|
|
2134
|
-
status: z2.enum(["draft", "committed", "proposed"])
|
|
2176
|
+
status: z2.enum(["draft", "committed", "proposed"]),
|
|
2177
|
+
classifiedBy: z2.enum(["llm", "heuristic", "explicit"]).optional(),
|
|
2178
|
+
confidence: z2.number().optional(),
|
|
2179
|
+
confidenceTier: z2.enum(["high", "medium", "low"]).optional()
|
|
2135
2180
|
})),
|
|
2136
2181
|
total: z2.number(),
|
|
2137
2182
|
failed: z2.number(),
|
|
2138
2183
|
committed: z2.number(),
|
|
2139
2184
|
proposed: z2.number(),
|
|
2140
2185
|
drafts: z2.number(),
|
|
2186
|
+
classified: z2.number().optional(),
|
|
2141
2187
|
autoCommitApplied: z2.boolean(),
|
|
2188
|
+
skippedLowConfidence: z2.array(z2.object({
|
|
2189
|
+
index: z2.number(),
|
|
2190
|
+
name: z2.string(),
|
|
2191
|
+
suggestedCollection: z2.string().optional(),
|
|
2192
|
+
confidence: z2.number().optional(),
|
|
2193
|
+
alternatives: z2.array(z2.object({
|
|
2194
|
+
collection: z2.string(),
|
|
2195
|
+
confidence: z2.number()
|
|
2196
|
+
})).optional()
|
|
2197
|
+
})).optional(),
|
|
2142
2198
|
failedEntries: z2.array(z2.object({
|
|
2143
2199
|
index: z2.number(),
|
|
2144
2200
|
collection: z2.string(),
|
|
@@ -2150,9 +2206,6 @@ function shouldAutoCommitCapture(autoCommit, governanceMode) {
|
|
|
2150
2206
|
return autoCommit === true || autoCommit === void 0 && governanceMode === "open";
|
|
2151
2207
|
}
|
|
2152
2208
|
function registerSmartCaptureTools(server) {
|
|
2153
|
-
const supportedCollections = new Set(
|
|
2154
|
-
CLASSIFIABLE_COLLECTIONS.filter((slug) => PROFILES.has(slug))
|
|
2155
|
-
);
|
|
2156
2209
|
const captureTool = server.registerTool(
|
|
2157
2210
|
"capture",
|
|
2158
2211
|
{
|
|
@@ -2175,7 +2228,6 @@ function registerSmartCaptureTools(server) {
|
|
|
2175
2228
|
name,
|
|
2176
2229
|
description,
|
|
2177
2230
|
classifierFlagOn,
|
|
2178
|
-
supportedCollections,
|
|
2179
2231
|
workspaceId: wsCtx.workspaceId,
|
|
2180
2232
|
explicitCollectionProvided
|
|
2181
2233
|
});
|
|
@@ -2642,7 +2694,7 @@ Use \`entries action=get\` to inspect the existing entry, or \`update-entry\` to
|
|
|
2642
2694
|
"batch-capture",
|
|
2643
2695
|
{
|
|
2644
2696
|
title: "Batch Capture",
|
|
2645
|
-
description: "Create multiple knowledge entries in one call. Ideal for workspace setup, document ingestion, or any scenario where you need to capture many entries at once.\n\nEach entry is created independently \u2014 if one fails, the others still succeed. Returns a compact summary instead of per-entry quality scorecards.\n\nAuto-linking runs per entry but contradiction checks and readiness hints are skipped for speed. Use `quality action=check` on individual entries afterward if needed.\n\nPass `autoCommit: false` to keep the whole batch draft-first. If omitted, Open mode workspaces commit by default and consensus/role modes keep drafts.",
|
|
2697
|
+
description: "Create multiple knowledge entries in one call. Ideal for workspace setup, document ingestion, or any scenario where you need to capture many entries at once.\n\nCollection is optional per entry \u2014 omit it and the LLM classifier auto-routes (FEAT-160). High/medium confidence entries are created; low confidence entries are skipped and returned with suggested collections for manual review.\n\nEach entry is created independently \u2014 if one fails, the others still succeed. Returns a compact summary instead of per-entry quality scorecards.\n\nAuto-linking runs per entry but contradiction checks and readiness hints are skipped for speed. Use `quality action=check` on individual entries afterward if needed.\n\nPass `autoCommit: false` to keep the whole batch draft-first. If omitted, Open mode workspaces commit by default and consensus/role modes keep drafts.",
|
|
2646
2698
|
inputSchema: batchCaptureSchema.shape,
|
|
2647
2699
|
annotations: { readOnlyHint: false, destructiveHint: false, openWorldHint: false }
|
|
2648
2700
|
},
|
|
@@ -2652,7 +2704,25 @@ Use \`entries action=get\` to inspect the existing entry, or \`update-entry\` to
|
|
|
2652
2704
|
const createdBy = agentId ? `agent:${agentId}` : "capture";
|
|
2653
2705
|
const wsCtx = await getWorkspaceContext();
|
|
2654
2706
|
const autoCommitApplied = shouldAutoCommitCapture(autoCommit, wsCtx.governanceMode);
|
|
2707
|
+
const needsClassification = entries.map((e, i) => ({ ...e, index: i })).filter((e) => !e.collection);
|
|
2708
|
+
let classificationMap = /* @__PURE__ */ new Map();
|
|
2709
|
+
if (needsClassification.length > 0) {
|
|
2710
|
+
await server.sendLoggingMessage({
|
|
2711
|
+
level: "info",
|
|
2712
|
+
data: `Classifying ${needsClassification.length}/${entries.length} entries via LLM...`,
|
|
2713
|
+
logger: "product-brain"
|
|
2714
|
+
});
|
|
2715
|
+
const classifications = await Promise.all(
|
|
2716
|
+
needsClassification.map(
|
|
2717
|
+
(e) => resolveCollection({ name: e.name, description: e.description }).catch(() => null)
|
|
2718
|
+
)
|
|
2719
|
+
);
|
|
2720
|
+
for (let i = 0; i < needsClassification.length; i++) {
|
|
2721
|
+
classificationMap.set(needsClassification[i].index, classifications[i]);
|
|
2722
|
+
}
|
|
2723
|
+
}
|
|
2655
2724
|
const results = [];
|
|
2725
|
+
const skippedLowConfidence = [];
|
|
2656
2726
|
await server.sendLoggingMessage({
|
|
2657
2727
|
level: "info",
|
|
2658
2728
|
data: `Batch capturing ${entries.length} entries...`,
|
|
@@ -2672,17 +2742,43 @@ Use \`entries action=get\` to inspect the existing entry, or \`update-entry\` to
|
|
|
2672
2742
|
logger: "product-brain"
|
|
2673
2743
|
});
|
|
2674
2744
|
}
|
|
2675
|
-
|
|
2676
|
-
|
|
2745
|
+
let resolvedSlug = entry.collection;
|
|
2746
|
+
let classifiedBy;
|
|
2747
|
+
let confidence;
|
|
2748
|
+
let confidenceTier;
|
|
2749
|
+
if (resolvedSlug) {
|
|
2750
|
+
classifiedBy = "explicit";
|
|
2751
|
+
} else {
|
|
2752
|
+
const resolved = classificationMap.get(entryIdx);
|
|
2753
|
+
if (!resolved || resolved.tier === "low") {
|
|
2754
|
+
skippedLowConfidence.push({
|
|
2755
|
+
index: entryIdx,
|
|
2756
|
+
name: entry.name,
|
|
2757
|
+
suggestedCollection: resolved?.collection,
|
|
2758
|
+
confidence: resolved?.confidence,
|
|
2759
|
+
alternatives: resolved?.alternatives.slice(0, 3)
|
|
2760
|
+
});
|
|
2761
|
+
continue;
|
|
2762
|
+
}
|
|
2763
|
+
resolvedSlug = resolved.collection;
|
|
2764
|
+
classifiedBy = resolved.classifiedBy;
|
|
2765
|
+
confidence = resolved.confidence;
|
|
2766
|
+
confidenceTier = resolved.tier;
|
|
2767
|
+
}
|
|
2768
|
+
const profile = PROFILES.get(resolvedSlug) ?? FALLBACK_PROFILE;
|
|
2769
|
+
const col = collCache.get(resolvedSlug);
|
|
2677
2770
|
if (!col) {
|
|
2678
2771
|
results.push({
|
|
2679
2772
|
name: entry.name,
|
|
2680
|
-
collection:
|
|
2773
|
+
collection: resolvedSlug,
|
|
2681
2774
|
entryId: "",
|
|
2682
2775
|
ok: false,
|
|
2683
2776
|
autoLinks: 0,
|
|
2684
2777
|
status: "draft",
|
|
2685
|
-
|
|
2778
|
+
classifiedBy,
|
|
2779
|
+
confidence,
|
|
2780
|
+
confidenceTier,
|
|
2781
|
+
error: `Collection "${resolvedSlug}" not found`
|
|
2686
2782
|
});
|
|
2687
2783
|
continue;
|
|
2688
2784
|
}
|
|
@@ -2705,7 +2801,7 @@ Use \`entries action=get\` to inspect the existing entry, or \`update-entry\` to
|
|
|
2705
2801
|
}
|
|
2706
2802
|
if (profile.inferField) {
|
|
2707
2803
|
const inferred = profile.inferField({
|
|
2708
|
-
collection:
|
|
2804
|
+
collection: resolvedSlug,
|
|
2709
2805
|
name: entry.name,
|
|
2710
2806
|
description: entry.description,
|
|
2711
2807
|
data,
|
|
@@ -2723,7 +2819,7 @@ Use \`entries action=get\` to inspect the existing entry, or \`update-entry\` to
|
|
|
2723
2819
|
}
|
|
2724
2820
|
try {
|
|
2725
2821
|
const result = await mcpMutation("chain.createEntry", {
|
|
2726
|
-
collectionSlug:
|
|
2822
|
+
collectionSlug: resolvedSlug,
|
|
2727
2823
|
entryId: entry.entryId ?? void 0,
|
|
2728
2824
|
name: entry.name,
|
|
2729
2825
|
status: "draft",
|
|
@@ -2742,7 +2838,7 @@ Use \`entries action=get\` to inspect the existing entry, or \`update-entry\` to
|
|
|
2742
2838
|
try {
|
|
2743
2839
|
const searchResults = await mcpQuery("chain.searchEntries", { query: searchQuery });
|
|
2744
2840
|
const candidates = (searchResults ?? []).filter((r) => r.entryId !== finalEntryId).map((r) => {
|
|
2745
|
-
const conf = computeLinkConfidence(r, entry.name, entry.description,
|
|
2841
|
+
const conf = computeLinkConfidence(r, entry.name, entry.description, resolvedSlug, collIdToSlug.get(r.collectionId) ?? "unknown");
|
|
2746
2842
|
return {
|
|
2747
2843
|
...r,
|
|
2748
2844
|
collSlug: collIdToSlug.get(r.collectionId) ?? "unknown",
|
|
@@ -2753,7 +2849,7 @@ Use \`entries action=get\` to inspect the existing entry, or \`update-entry\` to
|
|
|
2753
2849
|
if (autoLinkCount >= MAX_AUTO_LINKS) break;
|
|
2754
2850
|
if (c.confidence < AUTO_LINK_CONFIDENCE_THRESHOLD) break;
|
|
2755
2851
|
if (!c.entryId) continue;
|
|
2756
|
-
const { type: relationType } = inferRelationType(
|
|
2852
|
+
const { type: relationType } = inferRelationType(resolvedSlug, c.collSlug, profile);
|
|
2757
2853
|
try {
|
|
2758
2854
|
await mcpMutation("chain.createEntryRelation", {
|
|
2759
2855
|
fromEntryId: finalEntryId,
|
|
@@ -2785,22 +2881,28 @@ Use \`entries action=get\` to inspect the existing entry, or \`update-entry\` to
|
|
|
2785
2881
|
}
|
|
2786
2882
|
results.push({
|
|
2787
2883
|
name: entry.name,
|
|
2788
|
-
collection:
|
|
2884
|
+
collection: resolvedSlug,
|
|
2789
2885
|
entryId: finalEntryId,
|
|
2790
2886
|
ok: true,
|
|
2791
2887
|
autoLinks: autoLinkCount,
|
|
2792
2888
|
status: finalStatus,
|
|
2889
|
+
classifiedBy,
|
|
2890
|
+
confidence,
|
|
2891
|
+
confidenceTier,
|
|
2793
2892
|
...commitError ? { commitError } : {}
|
|
2794
2893
|
});
|
|
2795
2894
|
} catch (error) {
|
|
2796
2895
|
const msg = error instanceof Error ? error.message : String(error);
|
|
2797
2896
|
results.push({
|
|
2798
2897
|
name: entry.name,
|
|
2799
|
-
collection:
|
|
2898
|
+
collection: resolvedSlug,
|
|
2800
2899
|
entryId: "",
|
|
2801
2900
|
ok: false,
|
|
2802
2901
|
autoLinks: 0,
|
|
2803
2902
|
status: "draft",
|
|
2903
|
+
classifiedBy,
|
|
2904
|
+
confidence,
|
|
2905
|
+
confidenceTier,
|
|
2804
2906
|
error: msg
|
|
2805
2907
|
});
|
|
2806
2908
|
}
|
|
@@ -2811,9 +2913,10 @@ Use \`entries action=get\` to inspect the existing entry, or \`update-entry\` to
|
|
|
2811
2913
|
const proposed = created.filter((r) => r.status === "proposed");
|
|
2812
2914
|
const drafts = created.filter((r) => r.status === "draft");
|
|
2813
2915
|
const commitFailures = created.filter((r) => r.commitError);
|
|
2916
|
+
const classifiedCount = created.filter((r) => r.classifiedBy && r.classifiedBy !== "explicit").length;
|
|
2814
2917
|
await server.sendLoggingMessage({
|
|
2815
2918
|
level: "info",
|
|
2816
|
-
data: `Batch complete. ${created.length} succeeded, ${failed.length} failed.`,
|
|
2919
|
+
data: `Batch complete. ${created.length} succeeded, ${failed.length} failed, ${skippedLowConfidence.length} skipped (low confidence).`,
|
|
2817
2920
|
logger: "product-brain"
|
|
2818
2921
|
});
|
|
2819
2922
|
const totalAutoLinks = created.reduce((sum, r) => sum + r.autoLinks, 0);
|
|
@@ -2823,10 +2926,14 @@ Use \`entries action=get\` to inspect the existing entry, or \`update-entry\` to
|
|
|
2823
2926
|
}
|
|
2824
2927
|
const lines = [
|
|
2825
2928
|
`# Batch Capture Complete`,
|
|
2826
|
-
`**${created.length}** created, **${failed.length}** failed out of ${entries.length} total.`,
|
|
2929
|
+
`**${created.length}** created, **${failed.length}** failed, **${skippedLowConfidence.length}** skipped out of ${entries.length} total.`,
|
|
2827
2930
|
`**Auto-links created:** ${totalAutoLinks}`,
|
|
2828
2931
|
""
|
|
2829
2932
|
];
|
|
2933
|
+
if (classifiedCount > 0) {
|
|
2934
|
+
lines.push(`**Auto-classified:** ${classifiedCount} entries routed by LLM/heuristic.`);
|
|
2935
|
+
lines.push("");
|
|
2936
|
+
}
|
|
2830
2937
|
if (created.length > 0) {
|
|
2831
2938
|
lines.push(
|
|
2832
2939
|
`**Statuses:** ${committed.length} committed, ${proposed.length} proposed, ${drafts.length} draft.`
|
|
@@ -2841,12 +2948,38 @@ Use \`entries action=get\` to inspect the existing entry, or \`update-entry\` to
|
|
|
2841
2948
|
lines.push("");
|
|
2842
2949
|
}
|
|
2843
2950
|
if (created.length > 0) {
|
|
2844
|
-
|
|
2845
|
-
|
|
2846
|
-
|
|
2847
|
-
lines.push(
|
|
2951
|
+
const highConfidence = created.filter((r) => r.confidenceTier === "high" || r.classifiedBy === "explicit");
|
|
2952
|
+
const mediumConfidence = created.filter((r) => r.confidenceTier === "medium");
|
|
2953
|
+
if (highConfidence.length > 0) {
|
|
2954
|
+
lines.push("## Created \u2014 High Confidence / Explicit");
|
|
2955
|
+
for (const r of highConfidence) {
|
|
2956
|
+
const linkNote = r.autoLinks > 0 ? `, ${r.autoLinks} auto-links` : "";
|
|
2957
|
+
const classNote = r.classifiedBy !== "explicit" ? ` (${r.classifiedBy} ${r.confidence}%)` : "";
|
|
2958
|
+
lines.push(`- **${r.entryId}**: ${r.name} [${r.collection}] \u2014 \`${r.status}\`${classNote}${linkNote}`);
|
|
2959
|
+
}
|
|
2960
|
+
}
|
|
2961
|
+
if (mediumConfidence.length > 0) {
|
|
2962
|
+
lines.push("");
|
|
2963
|
+
lines.push("## Created \u2014 Medium Confidence (review recommended)");
|
|
2964
|
+
for (const r of mediumConfidence) {
|
|
2965
|
+
const linkNote = r.autoLinks > 0 ? `, ${r.autoLinks} auto-links` : "";
|
|
2966
|
+
lines.push(`- **${r.entryId}**: ${r.name} [${r.collection}] \u2014 \`${r.status}\` (${r.classifiedBy} ${r.confidence}%)${linkNote}`);
|
|
2967
|
+
}
|
|
2968
|
+
lines.push(`
|
|
2969
|
+
_Use \`entries action=move\` to correct any misclassified entries._`);
|
|
2848
2970
|
}
|
|
2849
2971
|
}
|
|
2972
|
+
if (skippedLowConfidence.length > 0) {
|
|
2973
|
+
lines.push("");
|
|
2974
|
+
lines.push("## Skipped \u2014 Low Confidence (needs explicit collection)");
|
|
2975
|
+
for (const s of skippedLowConfidence) {
|
|
2976
|
+
const suggestion = s.suggestedCollection ? ` \u2014 best guess: \`${s.suggestedCollection}\` (${s.confidence}%)` : " \u2014 no classification available";
|
|
2977
|
+
const alts = s.alternatives?.length ? ` | alternatives: ${s.alternatives.map((a) => `\`${a.collection}\` (${a.confidence}%)`).join(", ")}` : "";
|
|
2978
|
+
lines.push(`- **[${s.index}]** ${s.name}${suggestion}${alts}`);
|
|
2979
|
+
}
|
|
2980
|
+
lines.push("");
|
|
2981
|
+
lines.push("_Re-capture these with an explicit `collection` or use the suggested collection._");
|
|
2982
|
+
}
|
|
2850
2983
|
if (commitFailures.length > 0) {
|
|
2851
2984
|
lines.push("");
|
|
2852
2985
|
lines.push("## Saved as draft");
|
|
@@ -2864,21 +2997,36 @@ Use \`entries action=get\` to inspect the existing entry, or \`update-entry\` to
|
|
|
2864
2997
|
lines.push(`_If failed > 0, inspect \`failedEntries\` in the structured response and retry individually._`);
|
|
2865
2998
|
}
|
|
2866
2999
|
const entryIds = created.map((r) => r.entryId);
|
|
2867
|
-
if (entryIds.length > 0) {
|
|
3000
|
+
if (entryIds.length > 0 || skippedLowConfidence.length > 0) {
|
|
2868
3001
|
lines.push("");
|
|
2869
3002
|
lines.push("## Next Steps");
|
|
2870
|
-
|
|
3003
|
+
if (entryIds.length > 0) {
|
|
3004
|
+
lines.push(`- **Connect:** Run \`graph action=suggest\` on key entries to build the knowledge graph`);
|
|
3005
|
+
}
|
|
2871
3006
|
if (drafts.length > 0) {
|
|
2872
3007
|
lines.push(`- **Commit:** Use \`commit-entry\` to promote remaining drafts to SSOT`);
|
|
2873
3008
|
}
|
|
2874
|
-
|
|
3009
|
+
if (skippedLowConfidence.length > 0) {
|
|
3010
|
+
lines.push(`- **Classify:** Re-capture ${skippedLowConfidence.length} skipped entries with explicit collections`);
|
|
3011
|
+
}
|
|
3012
|
+
if (entryIds.length > 0) {
|
|
3013
|
+
lines.push(`- **Quality:** Run \`quality action=check\` on individual entries to assess completeness`);
|
|
3014
|
+
}
|
|
2875
3015
|
}
|
|
2876
|
-
const
|
|
3016
|
+
const skippedNote = skippedLowConfidence.length > 0 ? `, ${skippedLowConfidence.length} skipped (low confidence)` : "";
|
|
3017
|
+
const classifiedNote = classifiedCount > 0 ? `, ${classifiedCount} auto-classified` : "";
|
|
3018
|
+
const summary = failed.length > 0 || skippedLowConfidence.length > 0 ? `Batch captured ${created.length}/${entries.length} entries (${failed.length} failed${skippedNote}, ${committed.length} committed, ${proposed.length} proposed, ${drafts.length} draft${classifiedNote}).` : `Batch captured ${created.length} entries successfully (${committed.length} committed, ${proposed.length} proposed, ${drafts.length} draft${classifiedNote}).`;
|
|
2877
3019
|
const firstDraft = drafts[0];
|
|
2878
|
-
const next =
|
|
2879
|
-
|
|
2880
|
-
|
|
2881
|
-
|
|
3020
|
+
const next = [];
|
|
3021
|
+
if (created.length > 0) {
|
|
3022
|
+
next.push({ tool: "graph", description: "Discover connections", parameters: { action: "suggest", entryId: created[0].entryId } });
|
|
3023
|
+
}
|
|
3024
|
+
if (firstDraft) {
|
|
3025
|
+
next.push({ tool: "commit-entry", description: "Commit first draft", parameters: { entryId: firstDraft.entryId } });
|
|
3026
|
+
}
|
|
3027
|
+
if (skippedLowConfidence.length > 0) {
|
|
3028
|
+
next.push({ tool: "capture", description: `Capture skipped entry with explicit collection`, parameters: { name: skippedLowConfidence[0].name, collection: skippedLowConfidence[0].suggestedCollection ?? "" } });
|
|
3029
|
+
}
|
|
2882
3030
|
return {
|
|
2883
3031
|
content: [{ type: "text", text: lines.join("\n") }],
|
|
2884
3032
|
structuredContent: success(
|
|
@@ -2888,14 +3036,27 @@ Use \`entries action=get\` to inspect the existing entry, or \`update-entry\` to
|
|
|
2888
3036
|
entryId: r.entryId,
|
|
2889
3037
|
collection: r.collection,
|
|
2890
3038
|
name: r.name,
|
|
2891
|
-
status: r.status
|
|
3039
|
+
status: r.status,
|
|
3040
|
+
...r.classifiedBy ? { classifiedBy: r.classifiedBy } : {},
|
|
3041
|
+
...r.confidence != null ? { confidence: r.confidence } : {},
|
|
3042
|
+
...r.confidenceTier ? { confidenceTier: r.confidenceTier } : {}
|
|
2892
3043
|
})),
|
|
2893
3044
|
total: created.length,
|
|
2894
3045
|
failed: failed.length,
|
|
2895
3046
|
committed: committed.length,
|
|
2896
3047
|
proposed: proposed.length,
|
|
2897
3048
|
drafts: drafts.length,
|
|
3049
|
+
...classifiedCount > 0 ? { classified: classifiedCount } : {},
|
|
2898
3050
|
autoCommitApplied,
|
|
3051
|
+
...skippedLowConfidence.length > 0 && {
|
|
3052
|
+
skippedLowConfidence: skippedLowConfidence.map((s) => ({
|
|
3053
|
+
index: s.index,
|
|
3054
|
+
name: s.name,
|
|
3055
|
+
...s.suggestedCollection ? { suggestedCollection: s.suggestedCollection } : {},
|
|
3056
|
+
...s.confidence != null ? { confidence: s.confidence } : {},
|
|
3057
|
+
...s.alternatives?.length ? { alternatives: s.alternatives } : {}
|
|
3058
|
+
}))
|
|
3059
|
+
},
|
|
2899
3060
|
...failed.length > 0 && {
|
|
2900
3061
|
failedEntries: failed.map((r) => ({
|
|
2901
3062
|
index: results.indexOf(r),
|
|
@@ -3146,7 +3307,7 @@ export {
|
|
|
3146
3307
|
CLASSIFIABLE_COLLECTIONS,
|
|
3147
3308
|
classifyCollection,
|
|
3148
3309
|
isClassificationAmbiguous,
|
|
3149
|
-
|
|
3310
|
+
resolveCollection,
|
|
3150
3311
|
success,
|
|
3151
3312
|
failure,
|
|
3152
3313
|
parseOrFail,
|
|
@@ -3156,6 +3317,7 @@ export {
|
|
|
3156
3317
|
notFoundResult,
|
|
3157
3318
|
validationResult,
|
|
3158
3319
|
withEnvelope,
|
|
3320
|
+
STARTER_COLLECTIONS,
|
|
3159
3321
|
inferStrategyCategory,
|
|
3160
3322
|
formatQualityReport,
|
|
3161
3323
|
checkEntryQuality,
|
|
@@ -3169,4 +3331,4 @@ export {
|
|
|
3169
3331
|
formatRubricCoaching,
|
|
3170
3332
|
formatRubricVerdictSection
|
|
3171
3333
|
};
|
|
3172
|
-
//# sourceMappingURL=chunk-
|
|
3334
|
+
//# sourceMappingURL=chunk-TH5AUVVM.js.map
|