@kylewadegrove/cutline-mcp-cli 0.6.0 → 0.6.2
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/auth/callback.d.ts +2 -1
- package/dist/auth/callback.js +7 -2
- package/dist/commands/login.d.ts +2 -0
- package/dist/commands/login.js +1 -1
- package/dist/commands/setup.js +2 -2
- package/dist/commands/upgrade.js +1 -1
- package/dist/servers/chunk-7N4HJ3KR.js +1024 -0
- package/dist/servers/chunk-DE7R7WKY.js +331 -0
- package/dist/servers/chunk-OP4EO6FV.js +454 -0
- package/dist/servers/cutline-server.js +200 -693
- package/dist/servers/data-client-PF2AI2MX.js +148 -0
- package/dist/servers/exploration-server.js +63 -142
- package/dist/servers/integrations-server.js +8 -22
- package/dist/servers/output-server.js +12 -25
- package/dist/servers/premortem-server.js +170 -127
- package/dist/servers/tools-server.js +26 -30
- package/package.json +1 -10
- package/dist/servers/chunk-7FHM2GD3.js +0 -5836
- package/dist/servers/chunk-IVWF7VYZ.js +0 -10086
- package/dist/servers/chunk-JBJYSV4P.js +0 -139
- package/dist/servers/chunk-PD2HN2R5.js +0 -908
- package/dist/servers/chunk-PU7TL6S3.js +0 -91
- package/dist/servers/chunk-TGSEURMN.js +0 -46
- package/dist/servers/pipeline-O5GJPNR4.js +0 -20
- package/dist/servers/premortem-handoff-XT4K3YDJ.js +0 -10
- package/dist/servers/score-history-HO5KRVGC.js +0 -6
|
@@ -1,221 +1,88 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
import {
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
detectConstraintConflicts
|
|
7
|
-
} from "./chunk-UBBAYTW3.js";
|
|
8
|
-
import {
|
|
9
|
-
recordScoreSnapshot
|
|
10
|
-
} from "./chunk-TGSEURMN.js";
|
|
11
|
-
import {
|
|
12
|
-
firestoreRetry
|
|
13
|
-
} from "./chunk-PU7TL6S3.js";
|
|
3
|
+
PMJsonSchema,
|
|
4
|
+
RunInputSchema
|
|
5
|
+
} from "./chunk-DE7R7WKY.js";
|
|
14
6
|
import {
|
|
15
7
|
isWriteTool
|
|
16
8
|
} from "./chunk-KMUSQOTJ.js";
|
|
17
9
|
import {
|
|
18
|
-
applyEditsLogic,
|
|
19
|
-
buildPdfBuffer,
|
|
20
|
-
chatWithPersona,
|
|
21
|
-
createLinearIssues,
|
|
22
|
-
generateAnswer,
|
|
23
|
-
generateChatSuggestion,
|
|
24
|
-
generateExplorationResponse,
|
|
25
|
-
generateTemplateResponse,
|
|
26
|
-
generateTrialRun,
|
|
27
|
-
getPersona,
|
|
28
|
-
getWikiMarkdown,
|
|
29
|
-
listPersonas,
|
|
30
|
-
saveWikiMarkdown,
|
|
31
|
-
uploadAndSign
|
|
32
|
-
} from "./chunk-IVWF7VYZ.js";
|
|
33
|
-
import {
|
|
34
|
-
getStoredToken,
|
|
35
10
|
guardBoundary,
|
|
36
11
|
guardOutput,
|
|
37
|
-
initFirebase,
|
|
38
|
-
mapErrorToMcp,
|
|
39
12
|
perfTracker,
|
|
13
|
+
withPerfTracking
|
|
14
|
+
} from "./chunk-OP4EO6FV.js";
|
|
15
|
+
import {
|
|
16
|
+
addEdges,
|
|
17
|
+
addEntity,
|
|
18
|
+
addNodes,
|
|
19
|
+
addTestCases,
|
|
20
|
+
cfApplyEdits,
|
|
21
|
+
cfBuildAndUploadPdf,
|
|
22
|
+
cfChatWithPersona,
|
|
23
|
+
cfCreateLinearIssues,
|
|
24
|
+
cfGenerateAnswer,
|
|
25
|
+
cfGenerateChatSuggestion,
|
|
26
|
+
cfGenerateExplorationResponse,
|
|
27
|
+
cfGenerateTemplateResponse,
|
|
28
|
+
cfGenerateTrialRun,
|
|
29
|
+
cfGetWikiMarkdown,
|
|
30
|
+
cfPremortemRun,
|
|
31
|
+
cfRegenAssumptions,
|
|
32
|
+
cfRegenExperiments,
|
|
33
|
+
cfSaveWikiMarkdown,
|
|
34
|
+
createPremortem,
|
|
35
|
+
createTemplate,
|
|
36
|
+
generateReadinessReportViaProxy,
|
|
37
|
+
getAllBindings,
|
|
38
|
+
getAllEdges,
|
|
39
|
+
getAllEntities,
|
|
40
|
+
getAllNodes,
|
|
41
|
+
getAllNodesLight,
|
|
42
|
+
getEntitiesWithEmbeddings,
|
|
43
|
+
getGraphMetadata,
|
|
44
|
+
getIdeaReport,
|
|
45
|
+
getNodesByCategories,
|
|
46
|
+
getNodesMissingEmbeddings,
|
|
47
|
+
getNodesWithEmbeddings,
|
|
48
|
+
getPersona,
|
|
49
|
+
getPremortem,
|
|
50
|
+
getScanRateLimit,
|
|
51
|
+
getTemplate,
|
|
52
|
+
getTestCasesForEntity,
|
|
53
|
+
hasConstraints,
|
|
54
|
+
listPersonas,
|
|
55
|
+
listPremortems,
|
|
56
|
+
listTemplates,
|
|
57
|
+
mapErrorToMcp,
|
|
58
|
+
recordScoreSnapshot,
|
|
59
|
+
requirePremiumWithAutoAuth,
|
|
40
60
|
resolveAuthContext,
|
|
41
61
|
resolveAuthContextFree,
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
62
|
+
saveScanReport,
|
|
63
|
+
updateEntityEmbedding,
|
|
64
|
+
updateGraphMetadata,
|
|
65
|
+
updateNodeEmbeddings,
|
|
66
|
+
updatePremortem,
|
|
67
|
+
updateScanRateLimit,
|
|
68
|
+
updateTemplate,
|
|
69
|
+
upsertBindings,
|
|
70
|
+
upsertEdges,
|
|
71
|
+
upsertEntities,
|
|
72
|
+
upsertNodes,
|
|
73
|
+
validateRequestSize
|
|
74
|
+
} from "./chunk-7N4HJ3KR.js";
|
|
47
75
|
import {
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
runRolesAndSummarizeIncremental
|
|
54
|
-
} from "./chunk-7FHM2GD3.js";
|
|
55
|
-
import "./chunk-JBJYSV4P.js";
|
|
76
|
+
GraphTraverser,
|
|
77
|
+
computeGenericGraphMetrics,
|
|
78
|
+
computeMetricsFromGraph,
|
|
79
|
+
detectConstraintConflicts
|
|
80
|
+
} from "./chunk-UBBAYTW3.js";
|
|
56
81
|
|
|
57
82
|
// ../mcp/dist/mcp/src/cutline-server.js
|
|
58
83
|
import { Server } from "@modelcontextprotocol/sdk/server/index.js";
|
|
59
84
|
import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
|
|
60
85
|
import { CallToolRequestSchema, ErrorCode, ListToolsRequestSchema, McpError } from "@modelcontextprotocol/sdk/types.js";
|
|
61
|
-
import admin from "firebase-admin";
|
|
62
|
-
import { getApps as getApps4, getApp as getApp4 } from "firebase-admin/app";
|
|
63
|
-
import { getFirestore as getFirestore4 } from "firebase-admin/firestore";
|
|
64
|
-
|
|
65
|
-
// ../mcp/dist/mcp/src/context-graph/graph-store.js
|
|
66
|
-
import { getApps, getApp } from "firebase-admin/app";
|
|
67
|
-
import { getFirestore, Timestamp } from "firebase-admin/firestore";
|
|
68
|
-
function getNodesCollection(productId) {
|
|
69
|
-
const app = getApps().length > 0 ? getApp() : void 0;
|
|
70
|
-
if (!app)
|
|
71
|
-
throw new Error("Firebase not initialized");
|
|
72
|
-
const db2 = getFirestore(app);
|
|
73
|
-
return db2.collection("context_graph").doc(productId).collection("nodes");
|
|
74
|
-
}
|
|
75
|
-
var NODE_CACHE_TTL = 10 * 60 * 1e3;
|
|
76
|
-
var nodeCache = /* @__PURE__ */ new Map();
|
|
77
|
-
var lightNodeCache = /* @__PURE__ */ new Map();
|
|
78
|
-
function invalidateNodeCache(productId) {
|
|
79
|
-
nodeCache.delete(productId);
|
|
80
|
-
lightNodeCache.delete(productId);
|
|
81
|
-
}
|
|
82
|
-
var LIGHT_FIELDS = [
|
|
83
|
-
"category",
|
|
84
|
-
"summary",
|
|
85
|
-
"keywords",
|
|
86
|
-
"severity",
|
|
87
|
-
"action",
|
|
88
|
-
"source_type",
|
|
89
|
-
"source_id",
|
|
90
|
-
"ingested_at",
|
|
91
|
-
"confidence",
|
|
92
|
-
"threshold",
|
|
93
|
-
"conditions",
|
|
94
|
-
"scope",
|
|
95
|
-
"phase"
|
|
96
|
-
];
|
|
97
|
-
async function upsertNodes(productId, sourceType, sourceId, nodes) {
|
|
98
|
-
const collection = getNodesCollection(productId);
|
|
99
|
-
const db2 = getFirestore(getApp());
|
|
100
|
-
const batch = db2.batch();
|
|
101
|
-
const existingQuery = await collection.where("source_type", "==", sourceType).where("source_id", "==", sourceId).get();
|
|
102
|
-
existingQuery.docs.forEach((doc) => {
|
|
103
|
-
batch.delete(doc.ref);
|
|
104
|
-
});
|
|
105
|
-
for (const node of nodes) {
|
|
106
|
-
const docRef = collection.doc(node.id);
|
|
107
|
-
const nodeDoc = {
|
|
108
|
-
...node,
|
|
109
|
-
ingested_at: Timestamp.fromDate(node.ingested_at)
|
|
110
|
-
};
|
|
111
|
-
const cleaned = Object.fromEntries(Object.entries(nodeDoc).filter(([, v]) => v !== void 0));
|
|
112
|
-
batch.set(docRef, cleaned);
|
|
113
|
-
}
|
|
114
|
-
await firestoreRetry(() => batch.commit(), { operationName: "upsert_nodes" });
|
|
115
|
-
invalidateNodeCache(productId);
|
|
116
|
-
}
|
|
117
|
-
async function getAllNodes(productId) {
|
|
118
|
-
const cached = nodeCache.get(productId);
|
|
119
|
-
if (cached && Date.now() - cached.fetchedAt < NODE_CACHE_TTL) {
|
|
120
|
-
return cached.nodes;
|
|
121
|
-
}
|
|
122
|
-
const collection = getNodesCollection(productId);
|
|
123
|
-
const snapshot = await collection.get();
|
|
124
|
-
const nodes = snapshot.docs.map((doc) => {
|
|
125
|
-
const data = doc.data();
|
|
126
|
-
return {
|
|
127
|
-
...data,
|
|
128
|
-
id: doc.id,
|
|
129
|
-
ingested_at: data.ingested_at.toDate()
|
|
130
|
-
};
|
|
131
|
-
});
|
|
132
|
-
nodeCache.set(productId, { nodes, fetchedAt: Date.now() });
|
|
133
|
-
return nodes;
|
|
134
|
-
}
|
|
135
|
-
async function getAllNodesLight(productId) {
|
|
136
|
-
const cached = lightNodeCache.get(productId);
|
|
137
|
-
if (cached && Date.now() - cached.fetchedAt < NODE_CACHE_TTL) {
|
|
138
|
-
return cached.nodes;
|
|
139
|
-
}
|
|
140
|
-
const collection = getNodesCollection(productId);
|
|
141
|
-
const snapshot = await collection.select(...LIGHT_FIELDS).get();
|
|
142
|
-
const nodes = snapshot.docs.map((doc) => {
|
|
143
|
-
const data = doc.data();
|
|
144
|
-
return {
|
|
145
|
-
...data,
|
|
146
|
-
id: doc.id,
|
|
147
|
-
ingested_at: data.ingested_at?.toDate() ?? /* @__PURE__ */ new Date()
|
|
148
|
-
};
|
|
149
|
-
});
|
|
150
|
-
lightNodeCache.set(productId, { nodes, fetchedAt: Date.now() });
|
|
151
|
-
return nodes;
|
|
152
|
-
}
|
|
153
|
-
async function getNodesByCategories(productId, categories) {
|
|
154
|
-
const collection = getNodesCollection(productId);
|
|
155
|
-
const snapshot = await collection.where("category", "in", categories.slice(0, 10)).get();
|
|
156
|
-
return snapshot.docs.map((doc) => {
|
|
157
|
-
const data = doc.data();
|
|
158
|
-
return {
|
|
159
|
-
...data,
|
|
160
|
-
id: doc.id,
|
|
161
|
-
ingested_at: data.ingested_at.toDate()
|
|
162
|
-
};
|
|
163
|
-
});
|
|
164
|
-
}
|
|
165
|
-
async function hasConstraints(productId) {
|
|
166
|
-
const collection = getNodesCollection(productId);
|
|
167
|
-
const snapshot = await collection.limit(1).get();
|
|
168
|
-
return !snapshot.empty;
|
|
169
|
-
}
|
|
170
|
-
async function addNodes(productId, nodes) {
|
|
171
|
-
if (nodes.length === 0)
|
|
172
|
-
return;
|
|
173
|
-
const collection = getNodesCollection(productId);
|
|
174
|
-
const db2 = collection.firestore;
|
|
175
|
-
const MAX_BATCH = 490;
|
|
176
|
-
for (let i = 0; i < nodes.length; i += MAX_BATCH) {
|
|
177
|
-
const batch = db2.batch();
|
|
178
|
-
for (const node of nodes.slice(i, i + MAX_BATCH)) {
|
|
179
|
-
const ref = collection.doc(node.id);
|
|
180
|
-
const nodeDoc = { ...node };
|
|
181
|
-
if (node.ingested_at instanceof Date) {
|
|
182
|
-
nodeDoc.ingested_at = Timestamp.fromDate(node.ingested_at);
|
|
183
|
-
}
|
|
184
|
-
Object.keys(nodeDoc).forEach((k) => {
|
|
185
|
-
if (nodeDoc[k] === void 0)
|
|
186
|
-
delete nodeDoc[k];
|
|
187
|
-
});
|
|
188
|
-
batch.set(ref, nodeDoc, { merge: true });
|
|
189
|
-
}
|
|
190
|
-
await firestoreRetry(() => batch.commit(), { operationName: "add_nodes" });
|
|
191
|
-
}
|
|
192
|
-
invalidateNodeCache(productId);
|
|
193
|
-
}
|
|
194
|
-
async function getNodesWithEmbeddings(productId) {
|
|
195
|
-
const allNodes = await getAllNodes(productId);
|
|
196
|
-
return allNodes.filter((node) => node.embedding && node.embedding.length > 0);
|
|
197
|
-
}
|
|
198
|
-
async function updateNodeEmbeddings(productId, embeddingUpdates) {
|
|
199
|
-
if (embeddingUpdates.length === 0)
|
|
200
|
-
return;
|
|
201
|
-
const collection = getNodesCollection(productId);
|
|
202
|
-
const db2 = getFirestore(getApp());
|
|
203
|
-
const batchSize = 10;
|
|
204
|
-
for (let i = 0; i < embeddingUpdates.length; i += batchSize) {
|
|
205
|
-
const batch = db2.batch();
|
|
206
|
-
const updates = embeddingUpdates.slice(i, i + batchSize);
|
|
207
|
-
for (const { id, embedding } of updates) {
|
|
208
|
-
const docRef = collection.doc(id);
|
|
209
|
-
batch.update(docRef, { embedding });
|
|
210
|
-
}
|
|
211
|
-
await firestoreRetry(() => batch.commit(), { operationName: "update_node_embeddings" });
|
|
212
|
-
}
|
|
213
|
-
invalidateNodeCache(productId);
|
|
214
|
-
}
|
|
215
|
-
async function getNodesMissingEmbeddings(productId) {
|
|
216
|
-
const allNodes = await getAllNodes(productId);
|
|
217
|
-
return allNodes.filter((node) => !node.embedding || node.embedding.length === 0);
|
|
218
|
-
}
|
|
219
86
|
|
|
220
87
|
// ../mcp/dist/mcp/src/context-graph/embeddings.js
|
|
221
88
|
import { GoogleAuth } from "google-auth-library";
|
|
@@ -1993,147 +1860,6 @@ async function extractConstraintsFromDoc(docId, text, generateFn, options = {})
|
|
|
1993
1860
|
return convertExtractedToNodes(docId, extracted, options);
|
|
1994
1861
|
}
|
|
1995
1862
|
|
|
1996
|
-
// ../mcp/dist/mcp/src/context-graph/graph-store-v2.js
|
|
1997
|
-
import { getApps as getApps2, getApp as getApp2 } from "firebase-admin/app";
|
|
1998
|
-
import { getFirestore as getFirestore2, Timestamp as Timestamp2 } from "firebase-admin/firestore";
|
|
1999
|
-
function db() {
|
|
2000
|
-
const app = getApps2().length > 0 ? getApp2() : void 0;
|
|
2001
|
-
if (!app)
|
|
2002
|
-
throw new Error("Firebase not initialized");
|
|
2003
|
-
return getFirestore2(app);
|
|
2004
|
-
}
|
|
2005
|
-
function productRoot(productId) {
|
|
2006
|
-
return db().collection("context_graph").doc(productId);
|
|
2007
|
-
}
|
|
2008
|
-
function entitiesCol(productId) {
|
|
2009
|
-
return productRoot(productId).collection("entities");
|
|
2010
|
-
}
|
|
2011
|
-
function edgesCol(productId) {
|
|
2012
|
-
return productRoot(productId).collection("edges");
|
|
2013
|
-
}
|
|
2014
|
-
function bindingsCol(productId) {
|
|
2015
|
-
return productRoot(productId).collection("bindings");
|
|
2016
|
-
}
|
|
2017
|
-
function testCasesCol(productId) {
|
|
2018
|
-
return productRoot(productId).collection("test_cases");
|
|
2019
|
-
}
|
|
2020
|
-
function metaDoc(productId) {
|
|
2021
|
-
return productRoot(productId).collection("_meta").doc("graph");
|
|
2022
|
-
}
|
|
2023
|
-
function toEntityDoc(e) {
|
|
2024
|
-
return { ...e, ingested_at: Timestamp2.fromDate(e.ingested_at) };
|
|
2025
|
-
}
|
|
2026
|
-
function fromEntityDoc(id, data) {
|
|
2027
|
-
return { ...data, id, ingested_at: data.ingested_at.toDate() };
|
|
2028
|
-
}
|
|
2029
|
-
var BATCH_LIMIT = 500;
|
|
2030
|
-
async function commitInBatches(ops) {
|
|
2031
|
-
for (let i = 0; i < ops.length; i += BATCH_LIMIT) {
|
|
2032
|
-
await firestoreRetry(() => {
|
|
2033
|
-
const batch = db().batch();
|
|
2034
|
-
ops.slice(i, i + BATCH_LIMIT).forEach((op) => op(batch));
|
|
2035
|
-
return batch.commit();
|
|
2036
|
-
}, { operationName: "batch_commit" });
|
|
2037
|
-
}
|
|
2038
|
-
}
|
|
2039
|
-
async function upsertEntities(productId, sourceType, sourceId, entities) {
|
|
2040
|
-
const col = entitiesCol(productId);
|
|
2041
|
-
const existing = await col.where("source_type", "==", sourceType).where("source_id", "==", sourceId).get();
|
|
2042
|
-
const ops = [];
|
|
2043
|
-
existing.docs.forEach((doc) => {
|
|
2044
|
-
ops.push((b) => b.delete(doc.ref));
|
|
2045
|
-
});
|
|
2046
|
-
for (const entity of entities) {
|
|
2047
|
-
const ref = col.doc(entity.id);
|
|
2048
|
-
const doc = toEntityDoc(entity);
|
|
2049
|
-
ops.push((b) => b.set(ref, doc));
|
|
2050
|
-
}
|
|
2051
|
-
await commitInBatches(ops);
|
|
2052
|
-
}
|
|
2053
|
-
async function getAllEntities(productId) {
|
|
2054
|
-
const snap = await entitiesCol(productId).get();
|
|
2055
|
-
return snap.docs.map((d) => fromEntityDoc(d.id, d.data()));
|
|
2056
|
-
}
|
|
2057
|
-
async function addEntity(productId, entity) {
|
|
2058
|
-
const ref = entitiesCol(productId).doc(entity.id);
|
|
2059
|
-
await firestoreRetry(() => ref.set(toEntityDoc(entity), { merge: true }), { operationName: "entity_set" });
|
|
2060
|
-
}
|
|
2061
|
-
async function updateEntityEmbedding(productId, entityId, embedding) {
|
|
2062
|
-
await firestoreRetry(() => entitiesCol(productId).doc(entityId).update({ embedding }), { operationName: "entity_update_embedding" });
|
|
2063
|
-
}
|
|
2064
|
-
async function getEntitiesWithEmbeddings(productId) {
|
|
2065
|
-
const all = await getAllEntities(productId);
|
|
2066
|
-
return all.filter((e) => e.embedding && e.embedding.length > 0);
|
|
2067
|
-
}
|
|
2068
|
-
async function upsertEdges(productId, sourceId, edges) {
|
|
2069
|
-
const col = edgesCol(productId);
|
|
2070
|
-
const existing = await col.get();
|
|
2071
|
-
const ops = [];
|
|
2072
|
-
existing.docs.forEach((doc) => {
|
|
2073
|
-
if (doc.id.startsWith(sourceId)) {
|
|
2074
|
-
ops.push((b) => b.delete(doc.ref));
|
|
2075
|
-
}
|
|
2076
|
-
});
|
|
2077
|
-
for (const edge of edges) {
|
|
2078
|
-
const ref = col.doc(edge.id);
|
|
2079
|
-
ops.push((b) => b.set(ref, edge));
|
|
2080
|
-
}
|
|
2081
|
-
await commitInBatches(ops);
|
|
2082
|
-
}
|
|
2083
|
-
async function getAllEdges(productId) {
|
|
2084
|
-
const snap = await edgesCol(productId).get();
|
|
2085
|
-
return snap.docs.map((d) => ({ ...d.data(), id: d.id }));
|
|
2086
|
-
}
|
|
2087
|
-
async function addEdges(productId, edges) {
|
|
2088
|
-
const col = edgesCol(productId);
|
|
2089
|
-
const ops = [];
|
|
2090
|
-
for (const edge of edges) {
|
|
2091
|
-
const ref = col.doc(edge.id);
|
|
2092
|
-
ops.push((b) => b.set(ref, edge, { merge: true }));
|
|
2093
|
-
}
|
|
2094
|
-
await commitInBatches(ops);
|
|
2095
|
-
}
|
|
2096
|
-
async function upsertBindings(productId, bindings) {
|
|
2097
|
-
const col = bindingsCol(productId);
|
|
2098
|
-
const ops = [];
|
|
2099
|
-
for (const binding of bindings) {
|
|
2100
|
-
const ref = col.doc(binding.id);
|
|
2101
|
-
ops.push((b) => b.set(ref, binding, { merge: true }));
|
|
2102
|
-
}
|
|
2103
|
-
await commitInBatches(ops);
|
|
2104
|
-
}
|
|
2105
|
-
async function getAllBindings(productId) {
|
|
2106
|
-
const snap = await bindingsCol(productId).get();
|
|
2107
|
-
return snap.docs.map((d) => ({ ...d.data(), id: d.id }));
|
|
2108
|
-
}
|
|
2109
|
-
async function addTestCases(productId, cases) {
|
|
2110
|
-
const col = testCasesCol(productId);
|
|
2111
|
-
const ops = [];
|
|
2112
|
-
for (const tc of cases) {
|
|
2113
|
-
const ref = col.doc(tc.id);
|
|
2114
|
-
ops.push((b) => b.set(ref, tc, { merge: true }));
|
|
2115
|
-
}
|
|
2116
|
-
await commitInBatches(ops);
|
|
2117
|
-
}
|
|
2118
|
-
async function getTestCasesForEntity(productId, entityId) {
|
|
2119
|
-
const snap = await testCasesCol(productId).where("entity_id", "==", entityId).get();
|
|
2120
|
-
return snap.docs.map((d) => ({ ...d.data(), id: d.id }));
|
|
2121
|
-
}
|
|
2122
|
-
async function updateGraphMetadata(productId, meta) {
|
|
2123
|
-
const payload = { ...meta };
|
|
2124
|
-
if (meta.last_build) {
|
|
2125
|
-
payload.last_build = Timestamp2.fromDate(meta.last_build);
|
|
2126
|
-
}
|
|
2127
|
-
await firestoreRetry(() => metaDoc(productId).set(payload, { merge: true }), { operationName: "graph_metadata_set" });
|
|
2128
|
-
}
|
|
2129
|
-
async function getGraphMetadata(productId) {
|
|
2130
|
-
const doc = await metaDoc(productId).get();
|
|
2131
|
-
if (!doc.exists)
|
|
2132
|
-
return null;
|
|
2133
|
-
const data = doc.data();
|
|
2134
|
-
return { ...data, last_build: data.last_build?.toDate?.() ?? /* @__PURE__ */ new Date() };
|
|
2135
|
-
}
|
|
2136
|
-
|
|
2137
1863
|
// ../mcp/dist/mcp/src/adapters/requirements-adapter.js
|
|
2138
1864
|
var EXTRACTION_SYSTEM_PROMPT2 = `You are a requirements graph extractor. Given product requirements (PRD, user stories, specs), extract a structured graph of entities, relationships, and non-functional requirements.
|
|
2139
1865
|
|
|
@@ -2918,8 +2644,6 @@ function pct(value) {
|
|
|
2918
2644
|
}
|
|
2919
2645
|
|
|
2920
2646
|
// ../mcp/dist/mcp/src/context-graph/readiness-report.js
|
|
2921
|
-
import { getApp as getApp3, getApps as getApps3 } from "firebase-admin/app";
|
|
2922
|
-
import { getFirestore as getFirestore3 } from "firebase-admin/firestore";
|
|
2923
2647
|
function computeGrade(pct2) {
|
|
2924
2648
|
if (pct2 >= 90)
|
|
2925
2649
|
return "A+";
|
|
@@ -2931,76 +2655,6 @@ function computeGrade(pct2) {
|
|
|
2931
2655
|
return "C";
|
|
2932
2656
|
return "D";
|
|
2933
2657
|
}
|
|
2934
|
-
function reportIdFromProductId(productId) {
|
|
2935
|
-
return productId;
|
|
2936
|
-
}
|
|
2937
|
-
async function generateReadinessReport(input) {
|
|
2938
|
-
const app = getApps3().length > 0 ? getApp3() : void 0;
|
|
2939
|
-
if (!app)
|
|
2940
|
-
throw new Error("Firebase not initialized");
|
|
2941
|
-
const db2 = getFirestore3(app);
|
|
2942
|
-
const { productId, ownerUid } = input;
|
|
2943
|
-
const [entities, edges, constraintNodes, bindings] = await Promise.all([
|
|
2944
|
-
getAllEntities(productId),
|
|
2945
|
-
getAllEdges(productId),
|
|
2946
|
-
getAllNodes(productId),
|
|
2947
|
-
getAllBindings(productId)
|
|
2948
|
-
]);
|
|
2949
|
-
if (entities.length === 0 && constraintNodes.length === 0) {
|
|
2950
|
-
throw new Error(`No graph data for product "${productId}". Run graph_ingest_requirements or constraints_ingest first.`);
|
|
2951
|
-
}
|
|
2952
|
-
const metrics = computeMetricsFromGraph(entities, edges, constraintNodes, bindings);
|
|
2953
|
-
const readinessPct = metrics.engineering_readiness_pct ?? 0;
|
|
2954
|
-
const jobSnap = await db2.collection("premortem_jobs").doc(productId).get();
|
|
2955
|
-
const jobData = jobSnap.exists ? jobSnap.data() : void 0;
|
|
2956
|
-
const result = jobData?.result;
|
|
2957
|
-
const productName = result?.project?.name || jobData?.payload?.project?.name || "Untitled Product";
|
|
2958
|
-
const verdict = result?.decision?.recommendation;
|
|
2959
|
-
const techStack = entities.filter((e) => e.type === "component").map((e) => e.name).slice(0, 15);
|
|
2960
|
-
const reportId = reportIdFromProductId(productId);
|
|
2961
|
-
const now = /* @__PURE__ */ new Date();
|
|
2962
|
-
const report = {
|
|
2963
|
-
productId,
|
|
2964
|
-
productName,
|
|
2965
|
-
ownerUid,
|
|
2966
|
-
grade: computeGrade(readinessPct),
|
|
2967
|
-
readinessPct: Math.round(readinessPct),
|
|
2968
|
-
securityReadinessPct: Math.round(metrics.security_readiness_pct ?? 0),
|
|
2969
|
-
reliabilityReadinessPct: Math.round(metrics.reliability_readiness_pct ?? 0),
|
|
2970
|
-
scalabilityReadinessPct: Math.round(metrics.scalability_readiness_pct ?? 0),
|
|
2971
|
-
nfrCoverage: metrics.nfr_coverage,
|
|
2972
|
-
timeEstimate: {
|
|
2973
|
-
unassisted_hours: metrics.time_estimate.unassisted_hours,
|
|
2974
|
-
assisted_hours: metrics.time_estimate.assisted_hours,
|
|
2975
|
-
speedup_factor: metrics.time_estimate.speedup_factor
|
|
2976
|
-
},
|
|
2977
|
-
complexityFactors: {
|
|
2978
|
-
entity_count: metrics.complexity_factors.entity_count,
|
|
2979
|
-
nfr_count: metrics.complexity_factors.nfr_count,
|
|
2980
|
-
critical_nfr_count: metrics.complexity_factors.critical_nfr_count,
|
|
2981
|
-
pii_data_types: metrics.complexity_factors.pii_data_types,
|
|
2982
|
-
conflict_count: metrics.complexity_factors.conflict_count
|
|
2983
|
-
},
|
|
2984
|
-
rgrCompletedPhases: metrics.rgr_completed_phases ?? [],
|
|
2985
|
-
complianceFrameworks: metrics.compliance_frameworks ?? [],
|
|
2986
|
-
verdict,
|
|
2987
|
-
techStack,
|
|
2988
|
-
public: true,
|
|
2989
|
-
createdAt: now,
|
|
2990
|
-
updatedAt: now
|
|
2991
|
-
};
|
|
2992
|
-
const docRef = db2.collection("readiness_reports").doc(reportId);
|
|
2993
|
-
const existingSnap = await docRef.get();
|
|
2994
|
-
if (existingSnap.exists) {
|
|
2995
|
-
report.createdAt = existingSnap.data()?.createdAt?.toDate?.() ?? now;
|
|
2996
|
-
}
|
|
2997
|
-
await docRef.set({
|
|
2998
|
-
...report,
|
|
2999
|
-
createdAt: report.createdAt,
|
|
3000
|
-
updatedAt: now
|
|
3001
|
-
});
|
|
3002
|
-
return { report, reportId };
|
|
3003
|
-
}
|
|
3004
2658
|
var SITE_URL = "https://thecutline.ai";
|
|
3005
2659
|
function buildReportMarkdown(report, reportId) {
|
|
3006
2660
|
const cov = report.nfrCoverage;
|
|
@@ -3167,7 +2821,7 @@ async function propagateConstraints(productId, sourceEntityId, targetEntityId, s
|
|
|
3167
2821
|
// ../mcp/dist/src/orchestrator/agents/shared/vertex.js
|
|
3168
2822
|
import { VertexAI } from "@google-cloud/vertexai";
|
|
3169
2823
|
import { GoogleAuth as GoogleAuth2 } from "google-auth-library";
|
|
3170
|
-
import { Buffer
|
|
2824
|
+
import { Buffer } from "node:buffer";
|
|
3171
2825
|
|
|
3172
2826
|
// ../mcp/dist/src/shared/circuit-breaker.js
|
|
3173
2827
|
var DEFAULT_OPTIONS = {
|
|
@@ -3534,7 +3188,7 @@ ${options.system || ""}`.trim();
|
|
|
3534
3188
|
}
|
|
3535
3189
|
if (p?.inlineData?.data) {
|
|
3536
3190
|
try {
|
|
3537
|
-
const decoded =
|
|
3191
|
+
const decoded = Buffer.from(p.inlineData.data, "base64").toString("utf8");
|
|
3538
3192
|
inlineSegments.push(decoded);
|
|
3539
3193
|
} catch {
|
|
3540
3194
|
}
|
|
@@ -8918,7 +8572,6 @@ What's your connection to this space? Are you an insider who knows the pain, or
|
|
|
8918
8572
|
throw new McpError(ErrorCode.InvalidRequest, `Session not found: ${session_id}`);
|
|
8919
8573
|
}
|
|
8920
8574
|
session.conversationHistory.push({ role: "user", content: message });
|
|
8921
|
-
initFirebase();
|
|
8922
8575
|
const context = {
|
|
8923
8576
|
currentAct: session.currentAct,
|
|
8924
8577
|
initialInput: session.initialInput,
|
|
@@ -8927,7 +8580,7 @@ What's your connection to this space? Are you an insider who knows the pain, or
|
|
|
8927
8580
|
ideas: session.ideas,
|
|
8928
8581
|
conversationHistory: session.conversationHistory
|
|
8929
8582
|
};
|
|
8930
|
-
const response = await
|
|
8583
|
+
const response = await cfGenerateExplorationResponse(message, context);
|
|
8931
8584
|
session.conversationHistory.push({ role: "assistant", content: response.content });
|
|
8932
8585
|
if (response.artifacts) {
|
|
8933
8586
|
for (const artifact of response.artifacts) {
|
|
@@ -9050,14 +8703,8 @@ What's your connection to this space? Are you an insider who knows the pain, or
|
|
|
9050
8703
|
const valueScore = Math.round(idea.painIntensity * idea.aiLeverage * idea.marketAccessibility / 10);
|
|
9051
8704
|
let isPremium = false;
|
|
9052
8705
|
try {
|
|
9053
|
-
|
|
9054
|
-
|
|
9055
|
-
initFirebase(storedToken.environment);
|
|
9056
|
-
const decoded2 = await validateAuth(storedToken.refreshToken ? void 0 : storedToken.refreshToken);
|
|
9057
|
-
if (decoded2?.uid) {
|
|
9058
|
-
isPremium = await validateSubscription(decoded2.uid);
|
|
9059
|
-
}
|
|
9060
|
-
}
|
|
8706
|
+
await requirePremiumWithAutoAuth();
|
|
8707
|
+
isPremium = true;
|
|
9061
8708
|
} catch (e) {
|
|
9062
8709
|
}
|
|
9063
8710
|
if (!isPremium) {
|
|
@@ -9135,8 +8782,7 @@ Why AI: ${idea.whyAI}`
|
|
|
9135
8782
|
}
|
|
9136
8783
|
if (name2 === "trial_generate") {
|
|
9137
8784
|
const { prompt } = args;
|
|
9138
|
-
|
|
9139
|
-
const text = await generateTrialRun(prompt);
|
|
8785
|
+
const text = await cfGenerateTrialRun(prompt);
|
|
9140
8786
|
return { content: [{ type: "text", text: JSON.stringify({ text }) }] };
|
|
9141
8787
|
}
|
|
9142
8788
|
if (name2 === "engineering_audit") {
|
|
@@ -9146,27 +8792,20 @@ Why AI: ${idea.whyAI}`
|
|
|
9146
8792
|
}
|
|
9147
8793
|
const freeAuth = await resolveAuthContextFree(scanArgs.auth_token);
|
|
9148
8794
|
const uid = freeAuth.effectiveUid;
|
|
9149
|
-
|
|
9150
|
-
|
|
9151
|
-
if (rateLimitApp) {
|
|
9152
|
-
const rateLimitDb = getFirestore4(rateLimitApp);
|
|
9153
|
-
const profileRef = rateLimitDb.collection("users").doc(uid);
|
|
9154
|
-
const profileSnap = await profileRef.get();
|
|
9155
|
-
const profile = profileSnap.data() ?? {};
|
|
9156
|
-
const scanCounter = profile.free_scan_counter ?? { count: 0, reset_at: null };
|
|
8795
|
+
{
|
|
8796
|
+
const rateInfo = await getScanRateLimit();
|
|
9157
8797
|
const now = /* @__PURE__ */ new Date();
|
|
9158
|
-
const resetAt =
|
|
8798
|
+
const resetAt = rateInfo.periodStart ? new Date(rateInfo.periodStart) : /* @__PURE__ */ new Date(0);
|
|
9159
8799
|
const monthStart = new Date(now.getFullYear(), now.getMonth(), 1);
|
|
9160
8800
|
if (resetAt < monthStart) {
|
|
9161
|
-
await
|
|
9162
|
-
|
|
9163
|
-
|
|
9164
|
-
const hasPremium = await validateSubscription(uid);
|
|
9165
|
-
if (!hasPremium) {
|
|
8801
|
+
await updateScanRateLimit({ free_scan_counter: { count: 1, reset_at: now.toISOString() } });
|
|
8802
|
+
} else if (rateInfo.scanCount >= 3) {
|
|
8803
|
+
if (rateInfo.subscription !== "active" && rateInfo.subscription !== "trialing") {
|
|
9166
8804
|
throw new McpError(ErrorCode.InvalidRequest, "Free scan limit reached (3/month). Run `cutline-mcp upgrade` in your terminal, or visit https://thecutline.ai/upgrade");
|
|
9167
8805
|
}
|
|
8806
|
+
} else {
|
|
8807
|
+
await updateScanRateLimit({ free_scan_counter: { count: rateInfo.scanCount + 1, reset_at: rateInfo.periodStart ?? now.toISOString() } });
|
|
9168
8808
|
}
|
|
9169
|
-
await firestoreRetry(() => profileRef.set({ free_scan_counter: { count: admin.firestore.FieldValue.increment(1), reset_at: scanCounter.reset_at ?? now } }, { merge: true }), { operationName: "increment_scan_counter" });
|
|
9170
8809
|
}
|
|
9171
8810
|
const genericGraphId = buildGenericGraphId(uid, scanArgs.project_root);
|
|
9172
8811
|
const result = await handleSecurityScan(genericGraphId, uid, scanArgs, {
|
|
@@ -9182,44 +8821,36 @@ Why AI: ${idea.whyAI}`
|
|
|
9182
8821
|
updateGraphMetadata: (pid, patch) => updateGraphMetadata(pid, patch)
|
|
9183
8822
|
});
|
|
9184
8823
|
try {
|
|
9185
|
-
|
|
9186
|
-
await recordScoreSnapshot2(genericGraphId, result.metrics, "engineering_audit");
|
|
8824
|
+
await recordScoreSnapshot(genericGraphId, result.metrics, "engineering_audit");
|
|
9187
8825
|
} catch (e) {
|
|
9188
8826
|
console.error("[engineering_audit] Score snapshot failed (non-fatal):", e);
|
|
9189
8827
|
}
|
|
9190
8828
|
let reportId;
|
|
9191
8829
|
try {
|
|
9192
|
-
const
|
|
9193
|
-
|
|
9194
|
-
|
|
9195
|
-
|
|
9196
|
-
|
|
9197
|
-
|
|
9198
|
-
|
|
9199
|
-
|
|
9200
|
-
|
|
9201
|
-
|
|
9202
|
-
|
|
9203
|
-
|
|
9204
|
-
|
|
9205
|
-
|
|
9206
|
-
|
|
9207
|
-
|
|
9208
|
-
|
|
9209
|
-
|
|
9210
|
-
|
|
9211
|
-
|
|
9212
|
-
|
|
9213
|
-
|
|
9214
|
-
|
|
9215
|
-
|
|
9216
|
-
sca_summary: result.scaFindings ? {
|
|
9217
|
-
total: result.scaFindings.total,
|
|
9218
|
-
critical: result.scaFindings.critical,
|
|
9219
|
-
high: result.scaFindings.high
|
|
9220
|
-
} : null
|
|
9221
|
-
}), { operationName: "persist_scan_report" });
|
|
9222
|
-
}
|
|
8830
|
+
const saved = await saveScanReport({
|
|
8831
|
+
metrics: {
|
|
8832
|
+
engineering_readiness_pct: result.metrics.engineering_readiness_pct ?? 0,
|
|
8833
|
+
security_readiness_pct: result.metrics.security_readiness_pct ?? 0,
|
|
8834
|
+
reliability_readiness_pct: result.metrics.reliability_readiness_pct ?? 0,
|
|
8835
|
+
scalability_readiness_pct: result.metrics.scalability_readiness_pct ?? 0
|
|
8836
|
+
},
|
|
8837
|
+
ecosystem: result.ecosystem,
|
|
8838
|
+
binding_coverage_pct: result.bindingHealth.coverage_pct,
|
|
8839
|
+
frameworks_loaded: result.frameworksLoaded,
|
|
8840
|
+
sensitive_data_count: result.sensitiveDataCount,
|
|
8841
|
+
findings: result.gatedGapDetails.map((f) => ({
|
|
8842
|
+
title: f.title,
|
|
8843
|
+
severity: f.severity,
|
|
8844
|
+
category: f.category,
|
|
8845
|
+
description: f.description
|
|
8846
|
+
})),
|
|
8847
|
+
sca_summary: result.scaFindings ? {
|
|
8848
|
+
total: result.scaFindings.total,
|
|
8849
|
+
critical: result.scaFindings.critical,
|
|
8850
|
+
high: result.scaFindings.high
|
|
8851
|
+
} : null
|
|
8852
|
+
});
|
|
8853
|
+
reportId = saved.id;
|
|
9223
8854
|
} catch (e) {
|
|
9224
8855
|
console.error("[engineering_audit] Report persistence failed (non-fatal):", e);
|
|
9225
8856
|
}
|
|
@@ -9237,42 +8868,25 @@ Why AI: ${idea.whyAI}`
|
|
|
9237
8868
|
case "premortem_run": {
|
|
9238
8869
|
const { input } = args;
|
|
9239
8870
|
const parsedInput = RunInputSchema.parse(input);
|
|
9240
|
-
|
|
9241
|
-
|
|
9242
|
-
|
|
9243
|
-
|
|
9244
|
-
const runDb = getFirestore4(runApp);
|
|
9245
|
-
const runDocRef = runDb.collection("premortem_jobs").doc();
|
|
9246
|
-
if (!parsedInput.productId) {
|
|
9247
|
-
parsedInput.productId = runDocRef.id;
|
|
9248
|
-
}
|
|
9249
|
-
await firestoreRetry(() => runDocRef.set({
|
|
9250
|
-
status: "running",
|
|
9251
|
-
payload: parsedInput,
|
|
9252
|
-
createdAt: admin.firestore.FieldValue.serverTimestamp(),
|
|
9253
|
-
uid: effectiveUid || null
|
|
9254
|
-
}), { operationName: "premortem_run_set" });
|
|
8871
|
+
const jobId = parsedInput.productId || `pm_${Date.now()}_${Math.random().toString(36).slice(2, 9)}`;
|
|
8872
|
+
if (!parsedInput.productId)
|
|
8873
|
+
parsedInput.productId = jobId;
|
|
8874
|
+
const { id: runDocId } = await createPremortem({ status: "running", payload: parsedInput }, jobId);
|
|
9255
8875
|
const timeoutMs = 10 * 60 * 1e3;
|
|
9256
8876
|
const result = await Promise.race([
|
|
9257
|
-
|
|
8877
|
+
cfPremortemRun(parsedInput),
|
|
9258
8878
|
new Promise((_, reject) => setTimeout(() => reject(new Error("Timeout")), timeoutMs))
|
|
9259
8879
|
]);
|
|
9260
|
-
await
|
|
9261
|
-
return { content: [{ type: "text", text: JSON.stringify({ ...result, _jobId:
|
|
8880
|
+
await updatePremortem(runDocId, { status: "completed", result });
|
|
8881
|
+
return { content: [{ type: "text", text: JSON.stringify({ ...result, _jobId: runDocId, _graphProductId: parsedInput.productId }) }] };
|
|
9262
8882
|
}
|
|
9263
8883
|
case "premortem_from_idea": {
|
|
9264
8884
|
const { idea_report_id } = args;
|
|
9265
8885
|
if (!idea_report_id)
|
|
9266
8886
|
throw new McpError(ErrorCode.InvalidParams, "idea_report_id is required");
|
|
9267
|
-
|
|
9268
|
-
|
|
9269
|
-
if (!ftApp)
|
|
9270
|
-
throw new McpError(ErrorCode.InternalError, "Firebase not initialized");
|
|
9271
|
-
const ftDb = getFirestore4(ftApp);
|
|
9272
|
-
const ideaSnap = await firestoreRetry(() => ftDb.collection("idea_reports").doc(idea_report_id).get(), { operationName: "premortem_from_idea_read" });
|
|
9273
|
-
if (!ideaSnap.exists)
|
|
8887
|
+
const ideaData = await getIdeaReport(idea_report_id);
|
|
8888
|
+
if (!ideaData)
|
|
9274
8889
|
throw new McpError(ErrorCode.InvalidRequest, `Idea report not found: ${idea_report_id}`);
|
|
9275
|
-
const ideaData = ideaSnap.data();
|
|
9276
8890
|
const risks = (ideaData.risks ?? []).map((r) => r.title).join(", ");
|
|
9277
8891
|
const assumptions = (ideaData.assumptions ?? []).map((a) => a.title).join(", ");
|
|
9278
8892
|
const competitors = (ideaData.competitive_threats ?? []).map((t) => t.threat).join(", ");
|
|
@@ -9301,32 +8915,25 @@ Competitive threats: ${competitors}` : ""
|
|
|
9301
8915
|
competitors: (ideaData.competitive_threats ?? []).map((t) => t.threat)
|
|
9302
8916
|
}
|
|
9303
8917
|
});
|
|
9304
|
-
const
|
|
9305
|
-
ftInput.productId = ftDocRef.id;
|
|
9306
|
-
await firestoreRetry(() => ftDocRef.set({
|
|
8918
|
+
const { id: ftDocId } = await createPremortem({
|
|
9307
8919
|
status: "running",
|
|
9308
8920
|
payload: ftInput,
|
|
9309
8921
|
source: "fast_track_idea",
|
|
9310
|
-
idea_report_id
|
|
9311
|
-
|
|
9312
|
-
|
|
9313
|
-
}), { operationName: "premortem_from_idea_set" });
|
|
8922
|
+
idea_report_id
|
|
8923
|
+
});
|
|
8924
|
+
ftInput.productId = ftDocId;
|
|
9314
8925
|
const ftTimeoutMs = 10 * 60 * 1e3;
|
|
9315
8926
|
const ftResult = await Promise.race([
|
|
9316
|
-
|
|
8927
|
+
cfPremortemRun(ftInput),
|
|
9317
8928
|
new Promise((_, reject) => setTimeout(() => reject(new Error("Timeout")), ftTimeoutMs))
|
|
9318
8929
|
]);
|
|
9319
|
-
await
|
|
9320
|
-
status: "completed",
|
|
9321
|
-
result: ftResult,
|
|
9322
|
-
finishedAt: admin.firestore.FieldValue.serverTimestamp()
|
|
9323
|
-
}), { operationName: "premortem_from_idea_complete" });
|
|
8930
|
+
await updatePremortem(ftDocId, { status: "completed", result: ftResult });
|
|
9324
8931
|
return {
|
|
9325
8932
|
content: [{
|
|
9326
8933
|
type: "text",
|
|
9327
8934
|
text: JSON.stringify({
|
|
9328
8935
|
...ftResult,
|
|
9329
|
-
_jobId:
|
|
8936
|
+
_jobId: ftDocId,
|
|
9330
8937
|
_graphProductId: ftInput.productId,
|
|
9331
8938
|
_source: "fast_track_idea",
|
|
9332
8939
|
_idea_report_id: idea_report_id,
|
|
@@ -9338,32 +8945,16 @@ Competitive threats: ${competitors}` : ""
|
|
|
9338
8945
|
case "premortem_queue": {
|
|
9339
8946
|
const { input } = args;
|
|
9340
8947
|
const parsedInput = RunInputSchema.parse(input);
|
|
9341
|
-
const
|
|
9342
|
-
if (!
|
|
9343
|
-
|
|
9344
|
-
|
|
9345
|
-
const docRef = db2.collection("premortem_jobs").doc();
|
|
9346
|
-
if (!parsedInput.productId) {
|
|
9347
|
-
parsedInput.productId = docRef.id;
|
|
9348
|
-
}
|
|
9349
|
-
await firestoreRetry(() => docRef.set({
|
|
9350
|
-
status: "queued",
|
|
9351
|
-
payload: parsedInput,
|
|
9352
|
-
createdAt: admin.firestore.FieldValue.serverTimestamp(),
|
|
9353
|
-
uid: effectiveUid || null
|
|
9354
|
-
}), { operationName: "premortem_queue_set" });
|
|
9355
|
-
return { content: [{ type: "text", text: JSON.stringify({ jobId: docRef.id, productId: parsedInput.productId }) }] };
|
|
8948
|
+
const { id: queuedId } = await createPremortem({ status: "queued", payload: parsedInput });
|
|
8949
|
+
if (!parsedInput.productId)
|
|
8950
|
+
parsedInput.productId = queuedId;
|
|
8951
|
+
return { content: [{ type: "text", text: JSON.stringify({ jobId: queuedId, productId: parsedInput.productId }) }] };
|
|
9356
8952
|
}
|
|
9357
8953
|
case "premortem_status": {
|
|
9358
8954
|
const { jobId } = args;
|
|
9359
|
-
const
|
|
9360
|
-
if (!
|
|
9361
|
-
throw new McpError(ErrorCode.InternalError, "Firebase not initialized");
|
|
9362
|
-
const db2 = getFirestore4(app);
|
|
9363
|
-
const snap = await db2.collection("premortem_jobs").doc(jobId).get();
|
|
9364
|
-
if (!snap.exists)
|
|
8955
|
+
const data = await getPremortem(jobId);
|
|
8956
|
+
if (!data)
|
|
9365
8957
|
throw new McpError(ErrorCode.InvalidRequest, "Job not found");
|
|
9366
|
-
const data = snap.data() || {};
|
|
9367
8958
|
return {
|
|
9368
8959
|
content: [{
|
|
9369
8960
|
type: "text",
|
|
@@ -9379,119 +8970,98 @@ Competitive threats: ${competitors}` : ""
|
|
|
9379
8970
|
}
|
|
9380
8971
|
case "premortem_kick": {
|
|
9381
8972
|
const { jobId } = args;
|
|
9382
|
-
const
|
|
9383
|
-
if (!
|
|
9384
|
-
throw new McpError(ErrorCode.InternalError, "Firebase not initialized");
|
|
9385
|
-
const db2 = getFirestore4(app);
|
|
9386
|
-
const ref = db2.collection("premortem_jobs").doc(jobId);
|
|
9387
|
-
const snap = await ref.get();
|
|
9388
|
-
if (!snap.exists)
|
|
8973
|
+
const data = await getPremortem(jobId);
|
|
8974
|
+
if (!data)
|
|
9389
8975
|
throw new McpError(ErrorCode.InvalidRequest, "Job not found");
|
|
9390
|
-
const data = snap.data() || {};
|
|
9391
8976
|
if (data.status === "completed") {
|
|
9392
8977
|
return { content: [{ type: "text", text: JSON.stringify({ kicked: false, status: "completed" }) }] };
|
|
9393
8978
|
}
|
|
9394
8979
|
const kickInput = data.payload;
|
|
9395
|
-
if (!kickInput.productId)
|
|
9396
|
-
kickInput.productId =
|
|
9397
|
-
}
|
|
9398
|
-
await firestoreRetry(() => ref.update({ status: "running", payload: kickInput, startedAt: admin.firestore.FieldValue.serverTimestamp() }), { operationName: "premortem_kick_start" });
|
|
9399
|
-
const update = async (patch) => {
|
|
9400
|
-
await firestoreRetry(() => ref.update({ ...patch, updatedAt: admin.firestore.FieldValue.serverTimestamp() }), { operationName: "premortem_kick_progress" });
|
|
9401
|
-
};
|
|
8980
|
+
if (!kickInput.productId)
|
|
8981
|
+
kickInput.productId = jobId;
|
|
8982
|
+
await updatePremortem(jobId, { status: "running", payload: kickInput });
|
|
9402
8983
|
const result = await Promise.race([
|
|
9403
|
-
|
|
8984
|
+
cfPremortemRun(kickInput),
|
|
9404
8985
|
new Promise((_, reject) => setTimeout(() => reject(new Error("Timeout")), 10 * 60 * 1e3))
|
|
9405
8986
|
]);
|
|
9406
8987
|
const doc = PMJsonSchema.parse(result);
|
|
9407
|
-
await
|
|
8988
|
+
await updatePremortem(jobId, { status: "completed", result: doc, progress: 1 });
|
|
9408
8989
|
return { content: [{ type: "text", text: JSON.stringify({ kicked: true, status: "completed", productId: kickInput.productId }) }] };
|
|
9409
8990
|
}
|
|
9410
8991
|
case "premortem_list": {
|
|
9411
8992
|
const { limit = 10 } = args;
|
|
9412
|
-
const
|
|
9413
|
-
|
|
9414
|
-
|
|
9415
|
-
|
|
9416
|
-
|
|
9417
|
-
|
|
9418
|
-
|
|
9419
|
-
product_id: doc.data().payload?.productId || doc.id,
|
|
9420
|
-
status: doc.data().status,
|
|
9421
|
-
productName: doc.data().payload?.project?.name || doc.data().result?.project?.name || "Untitled",
|
|
9422
|
-
createdAt: doc.data().createdAt?.toDate()?.toISOString()
|
|
8993
|
+
const docs = await listPremortems({ limit });
|
|
8994
|
+
const premortems = docs.map((d) => ({
|
|
8995
|
+
id: d.id,
|
|
8996
|
+
product_id: d.payload?.productId || d.id,
|
|
8997
|
+
status: d.status,
|
|
8998
|
+
productName: d.payload?.project?.name || d.result?.project?.name || "Untitled",
|
|
8999
|
+
createdAt: d.createdAt?._seconds ? new Date(d.createdAt._seconds * 1e3).toISOString() : d.createdAt
|
|
9423
9000
|
}));
|
|
9424
9001
|
return { content: [{ type: "text", text: JSON.stringify(premortems, null, 2) }] };
|
|
9425
9002
|
}
|
|
9426
9003
|
case "premortem_render_pdf": {
|
|
9427
|
-
const { doc
|
|
9004
|
+
const { doc } = args;
|
|
9428
9005
|
const parsedDoc = PMJsonSchema.parse(doc);
|
|
9429
|
-
const
|
|
9430
|
-
|
|
9431
|
-
const uploaded = await uploadAndSign(bytes, parsedDoc?.project?.name || "premortem");
|
|
9432
|
-
if (uploaded?.url) {
|
|
9433
|
-
return { content: [{ type: "text", text: JSON.stringify({ url: uploaded.url }) }] };
|
|
9434
|
-
}
|
|
9435
|
-
}
|
|
9436
|
-
const base64 = Buffer.from(bytes).toString("base64");
|
|
9437
|
-
return { content: [{ type: "text", text: JSON.stringify({ dataUrl: `data:application/pdf;base64,${base64}` }) }] };
|
|
9006
|
+
const result = await cfBuildAndUploadPdf(parsedDoc);
|
|
9007
|
+
return { content: [{ type: "text", text: JSON.stringify(result) }] };
|
|
9438
9008
|
}
|
|
9439
9009
|
case "premortem_qa": {
|
|
9440
9010
|
const { question, doc } = args;
|
|
9441
|
-
const result = await
|
|
9011
|
+
const result = await cfGenerateAnswer(question, doc);
|
|
9442
9012
|
return { content: [{ type: "text", text: JSON.stringify(result) }] };
|
|
9443
9013
|
}
|
|
9444
9014
|
case "premortem_regen_assumptions": {
|
|
9445
9015
|
const { input, doc } = args;
|
|
9446
|
-
const out = await
|
|
9016
|
+
const out = await cfRegenAssumptions(input, doc);
|
|
9447
9017
|
return { content: [{ type: "text", text: JSON.stringify(out) }] };
|
|
9448
9018
|
}
|
|
9449
9019
|
case "premortem_regen_experiments": {
|
|
9450
9020
|
const { input, doc } = args;
|
|
9451
|
-
const out = await
|
|
9021
|
+
const out = await cfRegenExperiments(input, doc);
|
|
9452
9022
|
return { content: [{ type: "text", text: JSON.stringify(out) }] };
|
|
9453
9023
|
}
|
|
9454
9024
|
// Personas tools
|
|
9455
9025
|
case "personas_list": {
|
|
9456
9026
|
const { productId } = args;
|
|
9457
|
-
const personas = await listPersonas(
|
|
9027
|
+
const personas = await listPersonas(productId);
|
|
9458
9028
|
return { content: [{ type: "text", text: JSON.stringify({ personas }) }] };
|
|
9459
9029
|
}
|
|
9460
9030
|
case "personas_get": {
|
|
9461
9031
|
const { personaId } = args;
|
|
9462
|
-
const persona = await getPersona(
|
|
9032
|
+
const persona = await getPersona(personaId);
|
|
9463
9033
|
return { content: [{ type: "text", text: JSON.stringify({ persona }) }] };
|
|
9464
9034
|
}
|
|
9465
9035
|
case "personas_chat": {
|
|
9466
9036
|
const { persona, userMessage, product, conversationHistory } = args;
|
|
9467
|
-
const result = await
|
|
9037
|
+
const result = await cfChatWithPersona(persona, userMessage, product, conversationHistory);
|
|
9468
9038
|
return { content: [{ type: "text", text: JSON.stringify(result) }] };
|
|
9469
9039
|
}
|
|
9470
9040
|
// Wiki tools
|
|
9471
9041
|
case "wiki_load": {
|
|
9472
9042
|
const { projectId } = args;
|
|
9473
|
-
const markdown = await
|
|
9043
|
+
const markdown = await cfGetWikiMarkdown(projectId);
|
|
9474
9044
|
return { content: [{ type: "text", text: JSON.stringify({ markdown }) }] };
|
|
9475
9045
|
}
|
|
9476
9046
|
case "wiki_save": {
|
|
9477
9047
|
const { projectId, markdown } = args;
|
|
9478
|
-
await
|
|
9048
|
+
await cfSaveWikiMarkdown(projectId, markdown);
|
|
9479
9049
|
return { content: [{ type: "text", text: JSON.stringify({ ok: true }) }] };
|
|
9480
9050
|
}
|
|
9481
9051
|
case "wiki_apply_edits": {
|
|
9482
9052
|
const { edits } = args;
|
|
9483
|
-
const result = await
|
|
9053
|
+
const result = await cfApplyEdits(edits);
|
|
9484
9054
|
return { content: [{ type: "text", text: JSON.stringify(result) }] };
|
|
9485
9055
|
}
|
|
9486
9056
|
case "agent_chat": {
|
|
9487
9057
|
const { prompt, wikiMarkdown } = args;
|
|
9488
|
-
const text = await
|
|
9058
|
+
const text = await cfGenerateChatSuggestion(prompt, wikiMarkdown);
|
|
9489
9059
|
return { content: [{ type: "text", text: JSON.stringify({ text }) }] };
|
|
9490
9060
|
}
|
|
9491
9061
|
// Integrations
|
|
9492
9062
|
case "integrations_create_issues": {
|
|
9493
9063
|
const { schema_json, limit } = args;
|
|
9494
|
-
const result = await
|
|
9064
|
+
const result = await cfCreateLinearIssues(schema_json, limit);
|
|
9495
9065
|
return { content: [{ type: "text", text: JSON.stringify(result) }] };
|
|
9496
9066
|
}
|
|
9497
9067
|
// ─────────────────────────────────────────────────────────────────
|
|
@@ -9545,15 +9115,10 @@ Meta: ${JSON.stringify(output.meta)}` }
|
|
|
9545
9115
|
if (!premortem_id || !product_id) {
|
|
9546
9116
|
throw new McpError(ErrorCode.InvalidParams, "premortem_id and product_id are required");
|
|
9547
9117
|
}
|
|
9548
|
-
const
|
|
9549
|
-
if (!
|
|
9550
|
-
throw new McpError(ErrorCode.InternalError, "Firebase not initialized");
|
|
9551
|
-
const db2 = getFirestore4(app);
|
|
9552
|
-
const snap = await db2.collection("premortem_jobs").doc(premortem_id).get();
|
|
9553
|
-
if (!snap.exists) {
|
|
9118
|
+
const data = await getPremortem(premortem_id);
|
|
9119
|
+
if (!data) {
|
|
9554
9120
|
throw new McpError(ErrorCode.InvalidRequest, "Premortem not found");
|
|
9555
9121
|
}
|
|
9556
|
-
const data = snap.data();
|
|
9557
9122
|
if (data?.status !== "completed" || !data?.result) {
|
|
9558
9123
|
throw new McpError(ErrorCode.InvalidRequest, "Premortem not completed yet");
|
|
9559
9124
|
}
|
|
@@ -9613,7 +9178,7 @@ Meta: ${JSON.stringify(output.meta)}` }
|
|
|
9613
9178
|
if (!persona_id || !product_id) {
|
|
9614
9179
|
throw new McpError(ErrorCode.InvalidParams, "persona_id and product_id are required");
|
|
9615
9180
|
}
|
|
9616
|
-
const persona = await getPersona(
|
|
9181
|
+
const persona = await getPersona(persona_id);
|
|
9617
9182
|
if (!persona) {
|
|
9618
9183
|
throw new McpError(ErrorCode.InvalidRequest, "Persona not found");
|
|
9619
9184
|
}
|
|
@@ -9638,7 +9203,7 @@ Meta: ${JSON.stringify(output.meta)}` }
|
|
|
9638
9203
|
if (!wiki_id || !product_id) {
|
|
9639
9204
|
throw new McpError(ErrorCode.InvalidParams, "wiki_id and product_id are required");
|
|
9640
9205
|
}
|
|
9641
|
-
const markdown = await
|
|
9206
|
+
const markdown = await cfGetWikiMarkdown(wiki_id);
|
|
9642
9207
|
if (!markdown) {
|
|
9643
9208
|
throw new McpError(ErrorCode.InvalidRequest, "Wiki not found or empty");
|
|
9644
9209
|
}
|
|
@@ -10035,7 +9600,6 @@ Meta: ${JSON.stringify(output.meta)}` }
|
|
|
10035
9600
|
if (!product_id || !entityName || !entity_type || !entityDesc) {
|
|
10036
9601
|
throw new McpError(ErrorCode.InvalidParams, "product_id, name, entity_type, and description are required");
|
|
10037
9602
|
}
|
|
10038
|
-
initFirebase();
|
|
10039
9603
|
const slug = entityName.toLowerCase().replace(/[^a-z0-9]+/g, "_").replace(/^_|_$/g, "").slice(0, 40);
|
|
10040
9604
|
const entityId = `${entity_type}:${slug}`;
|
|
10041
9605
|
const now = /* @__PURE__ */ new Date();
|
|
@@ -10185,24 +9749,15 @@ Meta: ${JSON.stringify(output.meta)}` }
|
|
|
10185
9749
|
// ─────────────────────────────────────────────────────────────────
|
|
10186
9750
|
case "template_list": {
|
|
10187
9751
|
const { type, deep_dive_id } = args;
|
|
10188
|
-
|
|
10189
|
-
|
|
10190
|
-
|
|
10191
|
-
|
|
10192
|
-
|
|
10193
|
-
|
|
10194
|
-
|
|
10195
|
-
|
|
10196
|
-
type: doc.data().type,
|
|
10197
|
-
status: doc.data().status,
|
|
10198
|
-
sourceDeepDiveId: doc.data().sourceDeepDiveId,
|
|
10199
|
-
currentAct: doc.data().discoverySession?.currentAct || 1,
|
|
10200
|
-
createdAt: doc.data().createdAt?.toDate?.()?.toISOString() || null,
|
|
10201
|
-
updatedAt: doc.data().updatedAt?.toDate?.()?.toISOString() || null
|
|
9752
|
+
let templates = (await listTemplates({ type })).map((d) => ({
|
|
9753
|
+
id: d.id,
|
|
9754
|
+
type: d.type,
|
|
9755
|
+
status: d.status,
|
|
9756
|
+
sourceDeepDiveId: d.sourceDeepDiveId,
|
|
9757
|
+
currentAct: d.discoverySession?.currentAct || 1,
|
|
9758
|
+
createdAt: d.createdAt?._seconds ? new Date(d.createdAt._seconds * 1e3).toISOString() : d.createdAt || null,
|
|
9759
|
+
updatedAt: d.updatedAt?._seconds ? new Date(d.updatedAt._seconds * 1e3).toISOString() : d.updatedAt || null
|
|
10202
9760
|
}));
|
|
10203
|
-
if (type) {
|
|
10204
|
-
templates = templates.filter((t) => t.type === type);
|
|
10205
|
-
}
|
|
10206
9761
|
if (deep_dive_id) {
|
|
10207
9762
|
templates = templates.filter((t) => t.sourceDeepDiveId === deep_dive_id);
|
|
10208
9763
|
}
|
|
@@ -10217,31 +9772,21 @@ Meta: ${JSON.stringify(output.meta)}` }
|
|
|
10217
9772
|
const { template_id } = args;
|
|
10218
9773
|
if (!template_id)
|
|
10219
9774
|
throw new McpError(ErrorCode.InvalidParams, "template_id is required");
|
|
10220
|
-
|
|
10221
|
-
|
|
10222
|
-
if (!app)
|
|
10223
|
-
throw new McpError(ErrorCode.InternalError, "Firebase not initialized");
|
|
10224
|
-
const db2 = getFirestore4(app);
|
|
10225
|
-
const doc = await db2.collection("templates").doc(template_id).get();
|
|
10226
|
-
if (!doc.exists) {
|
|
9775
|
+
const data = await getTemplate(template_id);
|
|
9776
|
+
if (!data)
|
|
10227
9777
|
throw new McpError(ErrorCode.InvalidRequest, "Template not found");
|
|
10228
|
-
}
|
|
10229
|
-
const data = doc.data();
|
|
10230
|
-
if (data?.uid !== effectiveUid) {
|
|
10231
|
-
throw new McpError(ErrorCode.InvalidRequest, "Not authorized to access this template");
|
|
10232
|
-
}
|
|
10233
9778
|
return {
|
|
10234
9779
|
content: [{
|
|
10235
9780
|
type: "text",
|
|
10236
9781
|
text: JSON.stringify({
|
|
10237
|
-
id:
|
|
9782
|
+
id: data.id,
|
|
10238
9783
|
type: data.type,
|
|
10239
9784
|
status: data.status,
|
|
10240
9785
|
sourceDeepDiveId: data.sourceDeepDiveId,
|
|
10241
9786
|
discoverySession: data.discoverySession,
|
|
10242
9787
|
output: data.output,
|
|
10243
|
-
createdAt: data.createdAt?.
|
|
10244
|
-
updatedAt: data.updatedAt?.
|
|
9788
|
+
createdAt: data.createdAt?._seconds ? new Date(data.createdAt._seconds * 1e3).toISOString() : data.createdAt || null,
|
|
9789
|
+
updatedAt: data.updatedAt?._seconds ? new Date(data.updatedAt._seconds * 1e3).toISOString() : data.updatedAt || null
|
|
10245
9790
|
})
|
|
10246
9791
|
}]
|
|
10247
9792
|
};
|
|
@@ -10252,18 +9797,10 @@ Meta: ${JSON.stringify(output.meta)}` }
|
|
|
10252
9797
|
throw new McpError(ErrorCode.InvalidParams, "type is required");
|
|
10253
9798
|
if (!deep_dive_id)
|
|
10254
9799
|
throw new McpError(ErrorCode.InvalidParams, "deep_dive_id is required");
|
|
10255
|
-
|
|
10256
|
-
|
|
10257
|
-
if (!app)
|
|
10258
|
-
throw new McpError(ErrorCode.InternalError, "Firebase not initialized");
|
|
10259
|
-
const db2 = getFirestore4(app);
|
|
10260
|
-
const deepDiveDoc = await db2.collection("premortem_jobs").doc(deep_dive_id).get();
|
|
10261
|
-
if (!deepDiveDoc.exists) {
|
|
9800
|
+
const deepDiveData = await getPremortem(deep_dive_id);
|
|
9801
|
+
if (!deepDiveData) {
|
|
10262
9802
|
throw new McpError(ErrorCode.InvalidRequest, "Deep Dive not found");
|
|
10263
9803
|
}
|
|
10264
|
-
if (deepDiveDoc.data()?.uid !== effectiveUid) {
|
|
10265
|
-
throw new McpError(ErrorCode.InvalidRequest, "Not authorized to access this Deep Dive");
|
|
10266
|
-
}
|
|
10267
9804
|
const templateId = `tpl_${Date.now().toString(36)}_${Math.random().toString(36).substring(2, 8)}`;
|
|
10268
9805
|
const now = /* @__PURE__ */ new Date();
|
|
10269
9806
|
let constraintThresholds = [];
|
|
@@ -10278,8 +9815,7 @@ Meta: ${JSON.stringify(output.meta)}` }
|
|
|
10278
9815
|
category: c.category
|
|
10279
9816
|
}));
|
|
10280
9817
|
}
|
|
10281
|
-
await
|
|
10282
|
-
uid: effectiveUid,
|
|
9818
|
+
await createTemplate({
|
|
10283
9819
|
type,
|
|
10284
9820
|
status: "discovery_active",
|
|
10285
9821
|
sourceDeepDiveId: deep_dive_id,
|
|
@@ -10290,10 +9826,8 @@ Meta: ${JSON.stringify(output.meta)}` }
|
|
|
10290
9826
|
...rgr_mode ? { rgr_mode: true } : {},
|
|
10291
9827
|
...constraintThresholds.length > 0 ? { constraintThresholds } : {}
|
|
10292
9828
|
}
|
|
10293
|
-
}
|
|
10294
|
-
|
|
10295
|
-
updatedAt: now
|
|
10296
|
-
}), { operationName: "template_create" });
|
|
9829
|
+
}
|
|
9830
|
+
}, templateId);
|
|
10297
9831
|
return {
|
|
10298
9832
|
content: [{
|
|
10299
9833
|
type: "text",
|
|
@@ -10311,19 +9845,10 @@ Meta: ${JSON.stringify(output.meta)}` }
|
|
|
10311
9845
|
throw new McpError(ErrorCode.InvalidParams, "template_id is required");
|
|
10312
9846
|
if (!message)
|
|
10313
9847
|
throw new McpError(ErrorCode.InvalidParams, "message is required");
|
|
10314
|
-
|
|
10315
|
-
|
|
10316
|
-
if (!app)
|
|
10317
|
-
throw new McpError(ErrorCode.InternalError, "Firebase not initialized");
|
|
10318
|
-
const db2 = getFirestore4(app);
|
|
10319
|
-
const templateDoc = await db2.collection("templates").doc(template_id).get();
|
|
10320
|
-
if (!templateDoc.exists) {
|
|
9848
|
+
const templateData = await getTemplate(template_id);
|
|
9849
|
+
if (!templateData) {
|
|
10321
9850
|
throw new McpError(ErrorCode.InvalidRequest, "Template not found");
|
|
10322
9851
|
}
|
|
10323
|
-
const templateData = templateDoc.data();
|
|
10324
|
-
if (templateData?.uid !== effectiveUid) {
|
|
10325
|
-
throw new McpError(ErrorCode.InvalidRequest, "Not authorized to access this template");
|
|
10326
|
-
}
|
|
10327
9852
|
if (templateData.status === "completed") {
|
|
10328
9853
|
return {
|
|
10329
9854
|
content: [{
|
|
@@ -10338,12 +9863,12 @@ Meta: ${JSON.stringify(output.meta)}` }
|
|
|
10338
9863
|
}
|
|
10339
9864
|
let deepDiveData = null;
|
|
10340
9865
|
if (templateData.sourceDeepDiveId) {
|
|
10341
|
-
const
|
|
10342
|
-
if (
|
|
10343
|
-
deepDiveData =
|
|
9866
|
+
const ddData = await getPremortem(templateData.sourceDeepDiveId);
|
|
9867
|
+
if (ddData?.result) {
|
|
9868
|
+
deepDiveData = ddData.result;
|
|
10344
9869
|
}
|
|
10345
9870
|
}
|
|
10346
|
-
const response = await
|
|
9871
|
+
const response = await cfGenerateTemplateResponse(message, {
|
|
10347
9872
|
templateType: templateData.type,
|
|
10348
9873
|
currentAct: templateData.discoverySession?.currentAct || 1,
|
|
10349
9874
|
deepDiveData,
|
|
@@ -10382,16 +9907,15 @@ Meta: ${JSON.stringify(output.meta)}` }
|
|
|
10382
9907
|
}
|
|
10383
9908
|
});
|
|
10384
9909
|
}
|
|
10385
|
-
await
|
|
9910
|
+
await updateTemplate(template_id, {
|
|
10386
9911
|
status: response.completed ? "completed" : "discovery_active",
|
|
10387
9912
|
discoverySession: {
|
|
10388
9913
|
currentAct: response.suggestedAct || templateData.discoverySession?.currentAct || 1,
|
|
10389
9914
|
messages: [...templateData.discoverySession?.messages || [], userMsg, assistantMsg],
|
|
10390
9915
|
extractedContext: updatedContext
|
|
10391
9916
|
},
|
|
10392
|
-
output: response.output || null
|
|
10393
|
-
|
|
10394
|
-
}), { operationName: "template_update" });
|
|
9917
|
+
output: response.output || null
|
|
9918
|
+
});
|
|
10395
9919
|
if (response.completed && templateData.type === "testing" && response.output?.testCases?.length > 0) {
|
|
10396
9920
|
const graphProductId = updatedContext.product_id || templateData.sourceDeepDiveId;
|
|
10397
9921
|
if (graphProductId) {
|
|
@@ -10834,7 +10358,6 @@ Raw counts: ${JSON.stringify({
|
|
|
10834
10358
|
if (!product_id) {
|
|
10835
10359
|
throw new McpError(ErrorCode.InvalidParams, "product_id is required");
|
|
10836
10360
|
}
|
|
10837
|
-
initFirebase();
|
|
10838
10361
|
const [entities, edges, constraintNodes, bindings, existingMeta] = await Promise.all([
|
|
10839
10362
|
getAllEntities(product_id),
|
|
10840
10363
|
getAllEdges(product_id),
|
|
@@ -10865,11 +10388,7 @@ Raw counts: ${JSON.stringify({
|
|
|
10865
10388
|
});
|
|
10866
10389
|
await recordScoreSnapshot(product_id, metrics, "metrics_refresh");
|
|
10867
10390
|
try {
|
|
10868
|
-
|
|
10869
|
-
if (metricsApp) {
|
|
10870
|
-
const metricsDb = getFirestore4(metricsApp);
|
|
10871
|
-
await firestoreRetry(() => metricsDb.collection("premortem_jobs").doc(product_id).update({ metrics }), { operationName: "graph_metrics_sync" });
|
|
10872
|
-
}
|
|
10391
|
+
await updatePremortem(product_id, { metrics });
|
|
10873
10392
|
} catch (e) {
|
|
10874
10393
|
console.error("Metrics sync to premortem_jobs failed (non-fatal):", e);
|
|
10875
10394
|
}
|
|
@@ -10890,7 +10409,6 @@ ${JSON.stringify(metrics, null, 2)}` }
|
|
|
10890
10409
|
if (!product_id || !file_path) {
|
|
10891
10410
|
throw new McpError(ErrorCode.InvalidParams, "product_id and file_path are required");
|
|
10892
10411
|
}
|
|
10893
|
-
initFirebase();
|
|
10894
10412
|
const [rgrEntities, rgrEdges, rgrConstraints, rgrBindings] = await Promise.all([
|
|
10895
10413
|
getAllEntities(product_id),
|
|
10896
10414
|
getAllEdges(product_id),
|
|
@@ -10959,7 +10477,6 @@ ${JSON.stringify(metrics, null, 2)}` }
|
|
|
10959
10477
|
if (!validPhases.includes(phase)) {
|
|
10960
10478
|
throw new McpError(ErrorCode.InvalidParams, `Invalid phase "${phase}". Must be one of: ${validPhases.join(", ")}`);
|
|
10961
10479
|
}
|
|
10962
|
-
initFirebase();
|
|
10963
10480
|
const meta = await getGraphMetadata(product_id);
|
|
10964
10481
|
const existing = meta?.rgr_completed_phases ?? [];
|
|
10965
10482
|
if (existing.includes(phase)) {
|
|
@@ -11021,7 +10538,6 @@ ${JSON.stringify(metrics, null, 2)}` }
|
|
|
11021
10538
|
if (!product_id) {
|
|
11022
10539
|
throw new McpError(ErrorCode.InvalidParams, "product_id is required");
|
|
11023
10540
|
}
|
|
11024
|
-
initFirebase();
|
|
11025
10541
|
const [genEntities, genEdges, genConstraints, genBindings] = await Promise.all([
|
|
11026
10542
|
getAllEntities(product_id),
|
|
11027
10543
|
getAllEdges(product_id),
|
|
@@ -11215,9 +10731,11 @@ Meta: ${JSON.stringify({
|
|
|
11215
10731
|
if (!product_id) {
|
|
11216
10732
|
throw new McpError(ErrorCode.InvalidParams, "product_id is required");
|
|
11217
10733
|
}
|
|
11218
|
-
const { report, reportId } = await
|
|
10734
|
+
const { report, reportId } = await generateReadinessReportViaProxy({
|
|
11219
10735
|
productId: product_id,
|
|
11220
|
-
ownerUid: effectiveUid
|
|
10736
|
+
ownerUid: effectiveUid,
|
|
10737
|
+
computeMetricsFromGraph,
|
|
10738
|
+
computeGrade
|
|
11221
10739
|
});
|
|
11222
10740
|
const reportMd = buildReportMarkdown(report, reportId);
|
|
11223
10741
|
const snippets = buildBadgeSnippets(report, reportId);
|
|
@@ -11286,15 +10804,8 @@ Meta: ${JSON.stringify({
|
|
|
11286
10804
|
addEdges: (pid, edges) => addEdges(pid, edges),
|
|
11287
10805
|
upsertBindings: (pid, bindings) => upsertBindings(pid, bindings),
|
|
11288
10806
|
createDeepDiveJob: async (payload) => {
|
|
11289
|
-
initFirebase();
|
|
11290
|
-
const jobApp = getApps4().length > 0 ? getApp4() : void 0;
|
|
11291
|
-
if (!jobApp)
|
|
11292
|
-
throw new McpError(ErrorCode.InternalError, "Firebase not initialized");
|
|
11293
|
-
const jobDb = getFirestore4(jobApp);
|
|
11294
|
-
const docRef = jobDb.collection("premortem_jobs").doc();
|
|
11295
10807
|
const runInput = {
|
|
11296
10808
|
mode: "product",
|
|
11297
|
-
productId: docRef.id,
|
|
11298
10809
|
project: {
|
|
11299
10810
|
name: payload.extracted.productName,
|
|
11300
10811
|
brief: payload.extracted.productBrief,
|
|
@@ -11317,16 +10828,16 @@ Meta: ${JSON.stringify({
|
|
|
11317
10828
|
artifacts.target_users = payload.extracted.targetUsers;
|
|
11318
10829
|
if (Object.keys(artifacts).length > 0)
|
|
11319
10830
|
runInput.conversational_artifacts = artifacts;
|
|
11320
|
-
|
|
10831
|
+
const { id: newJobId } = await createPremortem({
|
|
11321
10832
|
status: "queued",
|
|
11322
|
-
payload: runInput,
|
|
11323
|
-
createdAt: admin.firestore.FieldValue.serverTimestamp(),
|
|
11324
|
-
uid: effectiveUid || null,
|
|
10833
|
+
payload: { ...runInput, productId: void 0 },
|
|
11325
10834
|
source: "code_audit",
|
|
11326
10835
|
mode: "product",
|
|
11327
10836
|
codeContext: payload.codeContext || null
|
|
11328
|
-
})
|
|
11329
|
-
|
|
10837
|
+
});
|
|
10838
|
+
runInput.productId = newJobId;
|
|
10839
|
+
await updatePremortem(newJobId, { payload: runInput });
|
|
10840
|
+
return { jobId: newJobId };
|
|
11330
10841
|
}
|
|
11331
10842
|
});
|
|
11332
10843
|
const sections = [
|
|
@@ -11450,11 +10961,7 @@ These constraints will now fire via \`constraints_auto\` when you edit matched f
|
|
|
11450
10961
|
metrics: auditMetrics
|
|
11451
10962
|
});
|
|
11452
10963
|
await recordScoreSnapshot(auditArgs.product_id, auditMetrics, "code_audit");
|
|
11453
|
-
|
|
11454
|
-
if (auditApp) {
|
|
11455
|
-
const auditDb = getFirestore4(auditApp);
|
|
11456
|
-
await firestoreRetry(() => auditDb.collection("premortem_jobs").doc(auditArgs.product_id).update({ metrics: auditMetrics }), { operationName: "code_audit_sync_metrics" });
|
|
11457
|
-
}
|
|
10964
|
+
await updatePremortem(auditArgs.product_id, { metrics: auditMetrics });
|
|
11458
10965
|
} catch (e) {
|
|
11459
10966
|
console.error("Metrics recompute after code audit failed (non-fatal):", e);
|
|
11460
10967
|
}
|