@kylewadegrove/cutline-mcp-cli 0.6.0 → 0.6.1
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-IVWF7VYZ.js → chunk-M37M2UA4.js} +517 -95
- package/dist/servers/chunk-NUBIEJTU.js +398 -0
- package/dist/servers/chunk-OP4EO6FV.js +454 -0
- package/dist/servers/chunk-PQUAX5YW.js +464 -0
- package/dist/servers/cutline-server.js +157 -633
- package/dist/servers/data-client-2IQIMRIS.js +117 -0
- package/dist/servers/exploration-server.js +65 -142
- package/dist/servers/integrations-server.js +7 -7
- package/dist/servers/output-server.js +7 -7
- package/dist/servers/premortem-server.js +57 -116
- package/dist/servers/tools-server.js +7 -7
- package/package.json +1 -1
- 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/score-history-HO5KRVGC.js +0 -6
|
@@ -5,12 +5,6 @@ import {
|
|
|
5
5
|
computeMetricsFromGraph,
|
|
6
6
|
detectConstraintConflicts
|
|
7
7
|
} from "./chunk-UBBAYTW3.js";
|
|
8
|
-
import {
|
|
9
|
-
recordScoreSnapshot
|
|
10
|
-
} from "./chunk-TGSEURMN.js";
|
|
11
|
-
import {
|
|
12
|
-
firestoreRetry
|
|
13
|
-
} from "./chunk-PU7TL6S3.js";
|
|
14
8
|
import {
|
|
15
9
|
isWriteTool
|
|
16
10
|
} from "./chunk-KMUSQOTJ.js";
|
|
@@ -29,21 +23,60 @@ import {
|
|
|
29
23
|
listPersonas,
|
|
30
24
|
saveWikiMarkdown,
|
|
31
25
|
uploadAndSign
|
|
32
|
-
} from "./chunk-
|
|
26
|
+
} from "./chunk-M37M2UA4.js";
|
|
27
|
+
import "./chunk-JBJYSV4P.js";
|
|
33
28
|
import {
|
|
34
|
-
getStoredToken,
|
|
35
29
|
guardBoundary,
|
|
36
30
|
guardOutput,
|
|
37
|
-
initFirebase,
|
|
38
|
-
mapErrorToMcp,
|
|
39
31
|
perfTracker,
|
|
32
|
+
withPerfTracking
|
|
33
|
+
} from "./chunk-OP4EO6FV.js";
|
|
34
|
+
import {
|
|
35
|
+
addEdges,
|
|
36
|
+
addEntity,
|
|
37
|
+
addNodes,
|
|
38
|
+
addTestCases,
|
|
39
|
+
createPremortem,
|
|
40
|
+
createTemplate,
|
|
41
|
+
generateReadinessReportViaProxy,
|
|
42
|
+
getAllBindings,
|
|
43
|
+
getAllEdges,
|
|
44
|
+
getAllEntities,
|
|
45
|
+
getAllNodes,
|
|
46
|
+
getAllNodesLight,
|
|
47
|
+
getEntitiesWithEmbeddings,
|
|
48
|
+
getGraphMetadata,
|
|
49
|
+
getIdeaReport,
|
|
50
|
+
getNodesByCategories,
|
|
51
|
+
getNodesMissingEmbeddings,
|
|
52
|
+
getNodesWithEmbeddings,
|
|
53
|
+
getPremortem,
|
|
54
|
+
getScanRateLimit,
|
|
55
|
+
getTemplate,
|
|
56
|
+
getTestCasesForEntity,
|
|
57
|
+
hasConstraints,
|
|
58
|
+
listPremortems,
|
|
59
|
+
listTemplates,
|
|
60
|
+
recordScoreSnapshot,
|
|
61
|
+
saveScanReport,
|
|
62
|
+
updateEntityEmbedding,
|
|
63
|
+
updateGraphMetadata,
|
|
64
|
+
updateNodeEmbeddings,
|
|
65
|
+
updatePremortem,
|
|
66
|
+
updateScanRateLimit,
|
|
67
|
+
updateTemplate,
|
|
68
|
+
upsertBindings,
|
|
69
|
+
upsertEdges,
|
|
70
|
+
upsertEntities,
|
|
71
|
+
upsertNodes
|
|
72
|
+
} from "./chunk-PQUAX5YW.js";
|
|
73
|
+
import {
|
|
74
|
+
mapErrorToMcp,
|
|
75
|
+
requirePremiumWithAutoAuth,
|
|
40
76
|
resolveAuthContext,
|
|
41
77
|
resolveAuthContextFree,
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
validateSubscription,
|
|
45
|
-
withPerfTracking
|
|
46
|
-
} from "./chunk-PD2HN2R5.js";
|
|
78
|
+
validateRequestSize
|
|
79
|
+
} from "./chunk-NUBIEJTU.js";
|
|
47
80
|
import {
|
|
48
81
|
PMJsonSchema,
|
|
49
82
|
RunInputSchema,
|
|
@@ -52,170 +85,11 @@ import {
|
|
|
52
85
|
runRolesAndSummarize,
|
|
53
86
|
runRolesAndSummarizeIncremental
|
|
54
87
|
} from "./chunk-7FHM2GD3.js";
|
|
55
|
-
import "./chunk-JBJYSV4P.js";
|
|
56
88
|
|
|
57
89
|
// ../mcp/dist/mcp/src/cutline-server.js
|
|
58
90
|
import { Server } from "@modelcontextprotocol/sdk/server/index.js";
|
|
59
91
|
import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
|
|
60
92
|
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
93
|
|
|
220
94
|
// ../mcp/dist/mcp/src/context-graph/embeddings.js
|
|
221
95
|
import { GoogleAuth } from "google-auth-library";
|
|
@@ -1993,147 +1867,6 @@ async function extractConstraintsFromDoc(docId, text, generateFn, options = {})
|
|
|
1993
1867
|
return convertExtractedToNodes(docId, extracted, options);
|
|
1994
1868
|
}
|
|
1995
1869
|
|
|
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
1870
|
// ../mcp/dist/mcp/src/adapters/requirements-adapter.js
|
|
2138
1871
|
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
1872
|
|
|
@@ -2918,8 +2651,6 @@ function pct(value) {
|
|
|
2918
2651
|
}
|
|
2919
2652
|
|
|
2920
2653
|
// ../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
2654
|
function computeGrade(pct2) {
|
|
2924
2655
|
if (pct2 >= 90)
|
|
2925
2656
|
return "A+";
|
|
@@ -2931,76 +2662,6 @@ function computeGrade(pct2) {
|
|
|
2931
2662
|
return "C";
|
|
2932
2663
|
return "D";
|
|
2933
2664
|
}
|
|
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
2665
|
var SITE_URL = "https://thecutline.ai";
|
|
3005
2666
|
function buildReportMarkdown(report, reportId) {
|
|
3006
2667
|
const cov = report.nfrCoverage;
|
|
@@ -8918,7 +8579,6 @@ What's your connection to this space? Are you an insider who knows the pain, or
|
|
|
8918
8579
|
throw new McpError(ErrorCode.InvalidRequest, `Session not found: ${session_id}`);
|
|
8919
8580
|
}
|
|
8920
8581
|
session.conversationHistory.push({ role: "user", content: message });
|
|
8921
|
-
initFirebase();
|
|
8922
8582
|
const context = {
|
|
8923
8583
|
currentAct: session.currentAct,
|
|
8924
8584
|
initialInput: session.initialInput,
|
|
@@ -9050,14 +8710,8 @@ What's your connection to this space? Are you an insider who knows the pain, or
|
|
|
9050
8710
|
const valueScore = Math.round(idea.painIntensity * idea.aiLeverage * idea.marketAccessibility / 10);
|
|
9051
8711
|
let isPremium = false;
|
|
9052
8712
|
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
|
-
}
|
|
8713
|
+
await requirePremiumWithAutoAuth();
|
|
8714
|
+
isPremium = true;
|
|
9061
8715
|
} catch (e) {
|
|
9062
8716
|
}
|
|
9063
8717
|
if (!isPremium) {
|
|
@@ -9135,7 +8789,6 @@ Why AI: ${idea.whyAI}`
|
|
|
9135
8789
|
}
|
|
9136
8790
|
if (name2 === "trial_generate") {
|
|
9137
8791
|
const { prompt } = args;
|
|
9138
|
-
initFirebase();
|
|
9139
8792
|
const text = await generateTrialRun(prompt);
|
|
9140
8793
|
return { content: [{ type: "text", text: JSON.stringify({ text }) }] };
|
|
9141
8794
|
}
|
|
@@ -9146,27 +8799,20 @@ Why AI: ${idea.whyAI}`
|
|
|
9146
8799
|
}
|
|
9147
8800
|
const freeAuth = await resolveAuthContextFree(scanArgs.auth_token);
|
|
9148
8801
|
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 };
|
|
8802
|
+
{
|
|
8803
|
+
const rateInfo = await getScanRateLimit();
|
|
9157
8804
|
const now = /* @__PURE__ */ new Date();
|
|
9158
|
-
const resetAt =
|
|
8805
|
+
const resetAt = rateInfo.periodStart ? new Date(rateInfo.periodStart) : /* @__PURE__ */ new Date(0);
|
|
9159
8806
|
const monthStart = new Date(now.getFullYear(), now.getMonth(), 1);
|
|
9160
8807
|
if (resetAt < monthStart) {
|
|
9161
|
-
await
|
|
9162
|
-
|
|
9163
|
-
|
|
9164
|
-
const hasPremium = await validateSubscription(uid);
|
|
9165
|
-
if (!hasPremium) {
|
|
8808
|
+
await updateScanRateLimit({ free_scan_counter: { count: 1, reset_at: now.toISOString() } });
|
|
8809
|
+
} else if (rateInfo.scanCount >= 3) {
|
|
8810
|
+
if (rateInfo.subscription !== "active" && rateInfo.subscription !== "trialing") {
|
|
9166
8811
|
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
8812
|
}
|
|
8813
|
+
} else {
|
|
8814
|
+
await updateScanRateLimit({ free_scan_counter: { count: rateInfo.scanCount + 1, reset_at: rateInfo.periodStart ?? now.toISOString() } });
|
|
9168
8815
|
}
|
|
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
8816
|
}
|
|
9171
8817
|
const genericGraphId = buildGenericGraphId(uid, scanArgs.project_root);
|
|
9172
8818
|
const result = await handleSecurityScan(genericGraphId, uid, scanArgs, {
|
|
@@ -9182,44 +8828,36 @@ Why AI: ${idea.whyAI}`
|
|
|
9182
8828
|
updateGraphMetadata: (pid, patch) => updateGraphMetadata(pid, patch)
|
|
9183
8829
|
});
|
|
9184
8830
|
try {
|
|
9185
|
-
|
|
9186
|
-
await recordScoreSnapshot2(genericGraphId, result.metrics, "engineering_audit");
|
|
8831
|
+
await recordScoreSnapshot(genericGraphId, result.metrics, "engineering_audit");
|
|
9187
8832
|
} catch (e) {
|
|
9188
8833
|
console.error("[engineering_audit] Score snapshot failed (non-fatal):", e);
|
|
9189
8834
|
}
|
|
9190
8835
|
let reportId;
|
|
9191
8836
|
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
|
-
}
|
|
8837
|
+
const saved = await saveScanReport({
|
|
8838
|
+
metrics: {
|
|
8839
|
+
engineering_readiness_pct: result.metrics.engineering_readiness_pct ?? 0,
|
|
8840
|
+
security_readiness_pct: result.metrics.security_readiness_pct ?? 0,
|
|
8841
|
+
reliability_readiness_pct: result.metrics.reliability_readiness_pct ?? 0,
|
|
8842
|
+
scalability_readiness_pct: result.metrics.scalability_readiness_pct ?? 0
|
|
8843
|
+
},
|
|
8844
|
+
ecosystem: result.ecosystem,
|
|
8845
|
+
binding_coverage_pct: result.bindingHealth.coverage_pct,
|
|
8846
|
+
frameworks_loaded: result.frameworksLoaded,
|
|
8847
|
+
sensitive_data_count: result.sensitiveDataCount,
|
|
8848
|
+
findings: result.gatedGapDetails.map((f) => ({
|
|
8849
|
+
title: f.title,
|
|
8850
|
+
severity: f.severity,
|
|
8851
|
+
category: f.category,
|
|
8852
|
+
description: f.description
|
|
8853
|
+
})),
|
|
8854
|
+
sca_summary: result.scaFindings ? {
|
|
8855
|
+
total: result.scaFindings.total,
|
|
8856
|
+
critical: result.scaFindings.critical,
|
|
8857
|
+
high: result.scaFindings.high
|
|
8858
|
+
} : null
|
|
8859
|
+
});
|
|
8860
|
+
reportId = saved.id;
|
|
9223
8861
|
} catch (e) {
|
|
9224
8862
|
console.error("[engineering_audit] Report persistence failed (non-fatal):", e);
|
|
9225
8863
|
}
|
|
@@ -9237,42 +8875,25 @@ Why AI: ${idea.whyAI}`
|
|
|
9237
8875
|
case "premortem_run": {
|
|
9238
8876
|
const { input } = args;
|
|
9239
8877
|
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" });
|
|
8878
|
+
const jobId = parsedInput.productId || `pm_${Date.now()}_${Math.random().toString(36).slice(2, 9)}`;
|
|
8879
|
+
if (!parsedInput.productId)
|
|
8880
|
+
parsedInput.productId = jobId;
|
|
8881
|
+
const { id: runDocId } = await createPremortem({ status: "running", payload: parsedInput }, jobId);
|
|
9255
8882
|
const timeoutMs = 10 * 60 * 1e3;
|
|
9256
8883
|
const result = await Promise.race([
|
|
9257
8884
|
runRolesAndSummarize(parsedInput, { includeDiagnostics: true }),
|
|
9258
8885
|
new Promise((_, reject) => setTimeout(() => reject(new Error("Timeout")), timeoutMs))
|
|
9259
8886
|
]);
|
|
9260
|
-
await
|
|
9261
|
-
return { content: [{ type: "text", text: JSON.stringify({ ...result, _jobId:
|
|
8887
|
+
await updatePremortem(runDocId, { status: "completed", result });
|
|
8888
|
+
return { content: [{ type: "text", text: JSON.stringify({ ...result, _jobId: runDocId, _graphProductId: parsedInput.productId }) }] };
|
|
9262
8889
|
}
|
|
9263
8890
|
case "premortem_from_idea": {
|
|
9264
8891
|
const { idea_report_id } = args;
|
|
9265
8892
|
if (!idea_report_id)
|
|
9266
8893
|
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)
|
|
8894
|
+
const ideaData = await getIdeaReport(idea_report_id);
|
|
8895
|
+
if (!ideaData)
|
|
9274
8896
|
throw new McpError(ErrorCode.InvalidRequest, `Idea report not found: ${idea_report_id}`);
|
|
9275
|
-
const ideaData = ideaSnap.data();
|
|
9276
8897
|
const risks = (ideaData.risks ?? []).map((r) => r.title).join(", ");
|
|
9277
8898
|
const assumptions = (ideaData.assumptions ?? []).map((a) => a.title).join(", ");
|
|
9278
8899
|
const competitors = (ideaData.competitive_threats ?? []).map((t) => t.threat).join(", ");
|
|
@@ -9301,32 +8922,25 @@ Competitive threats: ${competitors}` : ""
|
|
|
9301
8922
|
competitors: (ideaData.competitive_threats ?? []).map((t) => t.threat)
|
|
9302
8923
|
}
|
|
9303
8924
|
});
|
|
9304
|
-
const
|
|
9305
|
-
ftInput.productId = ftDocRef.id;
|
|
9306
|
-
await firestoreRetry(() => ftDocRef.set({
|
|
8925
|
+
const { id: ftDocId } = await createPremortem({
|
|
9307
8926
|
status: "running",
|
|
9308
8927
|
payload: ftInput,
|
|
9309
8928
|
source: "fast_track_idea",
|
|
9310
|
-
idea_report_id
|
|
9311
|
-
|
|
9312
|
-
|
|
9313
|
-
}), { operationName: "premortem_from_idea_set" });
|
|
8929
|
+
idea_report_id
|
|
8930
|
+
});
|
|
8931
|
+
ftInput.productId = ftDocId;
|
|
9314
8932
|
const ftTimeoutMs = 10 * 60 * 1e3;
|
|
9315
8933
|
const ftResult = await Promise.race([
|
|
9316
8934
|
runRolesAndSummarize(ftInput, { includeDiagnostics: true }),
|
|
9317
8935
|
new Promise((_, reject) => setTimeout(() => reject(new Error("Timeout")), ftTimeoutMs))
|
|
9318
8936
|
]);
|
|
9319
|
-
await
|
|
9320
|
-
status: "completed",
|
|
9321
|
-
result: ftResult,
|
|
9322
|
-
finishedAt: admin.firestore.FieldValue.serverTimestamp()
|
|
9323
|
-
}), { operationName: "premortem_from_idea_complete" });
|
|
8937
|
+
await updatePremortem(ftDocId, { status: "completed", result: ftResult });
|
|
9324
8938
|
return {
|
|
9325
8939
|
content: [{
|
|
9326
8940
|
type: "text",
|
|
9327
8941
|
text: JSON.stringify({
|
|
9328
8942
|
...ftResult,
|
|
9329
|
-
_jobId:
|
|
8943
|
+
_jobId: ftDocId,
|
|
9330
8944
|
_graphProductId: ftInput.productId,
|
|
9331
8945
|
_source: "fast_track_idea",
|
|
9332
8946
|
_idea_report_id: idea_report_id,
|
|
@@ -9338,32 +8952,16 @@ Competitive threats: ${competitors}` : ""
|
|
|
9338
8952
|
case "premortem_queue": {
|
|
9339
8953
|
const { input } = args;
|
|
9340
8954
|
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 }) }] };
|
|
8955
|
+
const { id: queuedId } = await createPremortem({ status: "queued", payload: parsedInput });
|
|
8956
|
+
if (!parsedInput.productId)
|
|
8957
|
+
parsedInput.productId = queuedId;
|
|
8958
|
+
return { content: [{ type: "text", text: JSON.stringify({ jobId: queuedId, productId: parsedInput.productId }) }] };
|
|
9356
8959
|
}
|
|
9357
8960
|
case "premortem_status": {
|
|
9358
8961
|
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)
|
|
8962
|
+
const data = await getPremortem(jobId);
|
|
8963
|
+
if (!data)
|
|
9365
8964
|
throw new McpError(ErrorCode.InvalidRequest, "Job not found");
|
|
9366
|
-
const data = snap.data() || {};
|
|
9367
8965
|
return {
|
|
9368
8966
|
content: [{
|
|
9369
8967
|
type: "text",
|
|
@@ -9379,47 +8977,36 @@ Competitive threats: ${competitors}` : ""
|
|
|
9379
8977
|
}
|
|
9380
8978
|
case "premortem_kick": {
|
|
9381
8979
|
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)
|
|
8980
|
+
const data = await getPremortem(jobId);
|
|
8981
|
+
if (!data)
|
|
9389
8982
|
throw new McpError(ErrorCode.InvalidRequest, "Job not found");
|
|
9390
|
-
const data = snap.data() || {};
|
|
9391
8983
|
if (data.status === "completed") {
|
|
9392
8984
|
return { content: [{ type: "text", text: JSON.stringify({ kicked: false, status: "completed" }) }] };
|
|
9393
8985
|
}
|
|
9394
8986
|
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" });
|
|
8987
|
+
if (!kickInput.productId)
|
|
8988
|
+
kickInput.productId = jobId;
|
|
8989
|
+
await updatePremortem(jobId, { status: "running", payload: kickInput });
|
|
9399
8990
|
const update = async (patch) => {
|
|
9400
|
-
await
|
|
8991
|
+
await updatePremortem(jobId, patch);
|
|
9401
8992
|
};
|
|
9402
8993
|
const result = await Promise.race([
|
|
9403
8994
|
runRolesAndSummarizeIncremental(kickInput, update),
|
|
9404
8995
|
new Promise((_, reject) => setTimeout(() => reject(new Error("Timeout")), 10 * 60 * 1e3))
|
|
9405
8996
|
]);
|
|
9406
8997
|
const doc = PMJsonSchema.parse(result);
|
|
9407
|
-
await
|
|
8998
|
+
await updatePremortem(jobId, { status: "completed", result: doc, progress: 1 });
|
|
9408
8999
|
return { content: [{ type: "text", text: JSON.stringify({ kicked: true, status: "completed", productId: kickInput.productId }) }] };
|
|
9409
9000
|
}
|
|
9410
9001
|
case "premortem_list": {
|
|
9411
9002
|
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()
|
|
9003
|
+
const docs = await listPremortems({ limit });
|
|
9004
|
+
const premortems = docs.map((d) => ({
|
|
9005
|
+
id: d.id,
|
|
9006
|
+
product_id: d.payload?.productId || d.id,
|
|
9007
|
+
status: d.status,
|
|
9008
|
+
productName: d.payload?.project?.name || d.result?.project?.name || "Untitled",
|
|
9009
|
+
createdAt: d.createdAt?._seconds ? new Date(d.createdAt._seconds * 1e3).toISOString() : d.createdAt
|
|
9423
9010
|
}));
|
|
9424
9011
|
return { content: [{ type: "text", text: JSON.stringify(premortems, null, 2) }] };
|
|
9425
9012
|
}
|
|
@@ -9545,15 +9132,10 @@ Meta: ${JSON.stringify(output.meta)}` }
|
|
|
9545
9132
|
if (!premortem_id || !product_id) {
|
|
9546
9133
|
throw new McpError(ErrorCode.InvalidParams, "premortem_id and product_id are required");
|
|
9547
9134
|
}
|
|
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) {
|
|
9135
|
+
const data = await getPremortem(premortem_id);
|
|
9136
|
+
if (!data) {
|
|
9554
9137
|
throw new McpError(ErrorCode.InvalidRequest, "Premortem not found");
|
|
9555
9138
|
}
|
|
9556
|
-
const data = snap.data();
|
|
9557
9139
|
if (data?.status !== "completed" || !data?.result) {
|
|
9558
9140
|
throw new McpError(ErrorCode.InvalidRequest, "Premortem not completed yet");
|
|
9559
9141
|
}
|
|
@@ -10035,7 +9617,6 @@ Meta: ${JSON.stringify(output.meta)}` }
|
|
|
10035
9617
|
if (!product_id || !entityName || !entity_type || !entityDesc) {
|
|
10036
9618
|
throw new McpError(ErrorCode.InvalidParams, "product_id, name, entity_type, and description are required");
|
|
10037
9619
|
}
|
|
10038
|
-
initFirebase();
|
|
10039
9620
|
const slug = entityName.toLowerCase().replace(/[^a-z0-9]+/g, "_").replace(/^_|_$/g, "").slice(0, 40);
|
|
10040
9621
|
const entityId = `${entity_type}:${slug}`;
|
|
10041
9622
|
const now = /* @__PURE__ */ new Date();
|
|
@@ -10185,24 +9766,15 @@ Meta: ${JSON.stringify(output.meta)}` }
|
|
|
10185
9766
|
// ─────────────────────────────────────────────────────────────────
|
|
10186
9767
|
case "template_list": {
|
|
10187
9768
|
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
|
|
9769
|
+
let templates = (await listTemplates({ type })).map((d) => ({
|
|
9770
|
+
id: d.id,
|
|
9771
|
+
type: d.type,
|
|
9772
|
+
status: d.status,
|
|
9773
|
+
sourceDeepDiveId: d.sourceDeepDiveId,
|
|
9774
|
+
currentAct: d.discoverySession?.currentAct || 1,
|
|
9775
|
+
createdAt: d.createdAt?._seconds ? new Date(d.createdAt._seconds * 1e3).toISOString() : d.createdAt || null,
|
|
9776
|
+
updatedAt: d.updatedAt?._seconds ? new Date(d.updatedAt._seconds * 1e3).toISOString() : d.updatedAt || null
|
|
10202
9777
|
}));
|
|
10203
|
-
if (type) {
|
|
10204
|
-
templates = templates.filter((t) => t.type === type);
|
|
10205
|
-
}
|
|
10206
9778
|
if (deep_dive_id) {
|
|
10207
9779
|
templates = templates.filter((t) => t.sourceDeepDiveId === deep_dive_id);
|
|
10208
9780
|
}
|
|
@@ -10217,31 +9789,21 @@ Meta: ${JSON.stringify(output.meta)}` }
|
|
|
10217
9789
|
const { template_id } = args;
|
|
10218
9790
|
if (!template_id)
|
|
10219
9791
|
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) {
|
|
9792
|
+
const data = await getTemplate(template_id);
|
|
9793
|
+
if (!data)
|
|
10227
9794
|
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
9795
|
return {
|
|
10234
9796
|
content: [{
|
|
10235
9797
|
type: "text",
|
|
10236
9798
|
text: JSON.stringify({
|
|
10237
|
-
id:
|
|
9799
|
+
id: data.id,
|
|
10238
9800
|
type: data.type,
|
|
10239
9801
|
status: data.status,
|
|
10240
9802
|
sourceDeepDiveId: data.sourceDeepDiveId,
|
|
10241
9803
|
discoverySession: data.discoverySession,
|
|
10242
9804
|
output: data.output,
|
|
10243
|
-
createdAt: data.createdAt?.
|
|
10244
|
-
updatedAt: data.updatedAt?.
|
|
9805
|
+
createdAt: data.createdAt?._seconds ? new Date(data.createdAt._seconds * 1e3).toISOString() : data.createdAt || null,
|
|
9806
|
+
updatedAt: data.updatedAt?._seconds ? new Date(data.updatedAt._seconds * 1e3).toISOString() : data.updatedAt || null
|
|
10245
9807
|
})
|
|
10246
9808
|
}]
|
|
10247
9809
|
};
|
|
@@ -10252,18 +9814,10 @@ Meta: ${JSON.stringify(output.meta)}` }
|
|
|
10252
9814
|
throw new McpError(ErrorCode.InvalidParams, "type is required");
|
|
10253
9815
|
if (!deep_dive_id)
|
|
10254
9816
|
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) {
|
|
9817
|
+
const deepDiveData = await getPremortem(deep_dive_id);
|
|
9818
|
+
if (!deepDiveData) {
|
|
10262
9819
|
throw new McpError(ErrorCode.InvalidRequest, "Deep Dive not found");
|
|
10263
9820
|
}
|
|
10264
|
-
if (deepDiveDoc.data()?.uid !== effectiveUid) {
|
|
10265
|
-
throw new McpError(ErrorCode.InvalidRequest, "Not authorized to access this Deep Dive");
|
|
10266
|
-
}
|
|
10267
9821
|
const templateId = `tpl_${Date.now().toString(36)}_${Math.random().toString(36).substring(2, 8)}`;
|
|
10268
9822
|
const now = /* @__PURE__ */ new Date();
|
|
10269
9823
|
let constraintThresholds = [];
|
|
@@ -10278,8 +9832,7 @@ Meta: ${JSON.stringify(output.meta)}` }
|
|
|
10278
9832
|
category: c.category
|
|
10279
9833
|
}));
|
|
10280
9834
|
}
|
|
10281
|
-
await
|
|
10282
|
-
uid: effectiveUid,
|
|
9835
|
+
await createTemplate({
|
|
10283
9836
|
type,
|
|
10284
9837
|
status: "discovery_active",
|
|
10285
9838
|
sourceDeepDiveId: deep_dive_id,
|
|
@@ -10290,10 +9843,8 @@ Meta: ${JSON.stringify(output.meta)}` }
|
|
|
10290
9843
|
...rgr_mode ? { rgr_mode: true } : {},
|
|
10291
9844
|
...constraintThresholds.length > 0 ? { constraintThresholds } : {}
|
|
10292
9845
|
}
|
|
10293
|
-
}
|
|
10294
|
-
|
|
10295
|
-
updatedAt: now
|
|
10296
|
-
}), { operationName: "template_create" });
|
|
9846
|
+
}
|
|
9847
|
+
}, templateId);
|
|
10297
9848
|
return {
|
|
10298
9849
|
content: [{
|
|
10299
9850
|
type: "text",
|
|
@@ -10311,19 +9862,10 @@ Meta: ${JSON.stringify(output.meta)}` }
|
|
|
10311
9862
|
throw new McpError(ErrorCode.InvalidParams, "template_id is required");
|
|
10312
9863
|
if (!message)
|
|
10313
9864
|
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) {
|
|
9865
|
+
const templateData = await getTemplate(template_id);
|
|
9866
|
+
if (!templateData) {
|
|
10321
9867
|
throw new McpError(ErrorCode.InvalidRequest, "Template not found");
|
|
10322
9868
|
}
|
|
10323
|
-
const templateData = templateDoc.data();
|
|
10324
|
-
if (templateData?.uid !== effectiveUid) {
|
|
10325
|
-
throw new McpError(ErrorCode.InvalidRequest, "Not authorized to access this template");
|
|
10326
|
-
}
|
|
10327
9869
|
if (templateData.status === "completed") {
|
|
10328
9870
|
return {
|
|
10329
9871
|
content: [{
|
|
@@ -10338,9 +9880,9 @@ Meta: ${JSON.stringify(output.meta)}` }
|
|
|
10338
9880
|
}
|
|
10339
9881
|
let deepDiveData = null;
|
|
10340
9882
|
if (templateData.sourceDeepDiveId) {
|
|
10341
|
-
const
|
|
10342
|
-
if (
|
|
10343
|
-
deepDiveData =
|
|
9883
|
+
const ddData = await getPremortem(templateData.sourceDeepDiveId);
|
|
9884
|
+
if (ddData?.result) {
|
|
9885
|
+
deepDiveData = ddData.result;
|
|
10344
9886
|
}
|
|
10345
9887
|
}
|
|
10346
9888
|
const response = await generateTemplateResponse(message, {
|
|
@@ -10382,16 +9924,15 @@ Meta: ${JSON.stringify(output.meta)}` }
|
|
|
10382
9924
|
}
|
|
10383
9925
|
});
|
|
10384
9926
|
}
|
|
10385
|
-
await
|
|
9927
|
+
await updateTemplate(template_id, {
|
|
10386
9928
|
status: response.completed ? "completed" : "discovery_active",
|
|
10387
9929
|
discoverySession: {
|
|
10388
9930
|
currentAct: response.suggestedAct || templateData.discoverySession?.currentAct || 1,
|
|
10389
9931
|
messages: [...templateData.discoverySession?.messages || [], userMsg, assistantMsg],
|
|
10390
9932
|
extractedContext: updatedContext
|
|
10391
9933
|
},
|
|
10392
|
-
output: response.output || null
|
|
10393
|
-
|
|
10394
|
-
}), { operationName: "template_update" });
|
|
9934
|
+
output: response.output || null
|
|
9935
|
+
});
|
|
10395
9936
|
if (response.completed && templateData.type === "testing" && response.output?.testCases?.length > 0) {
|
|
10396
9937
|
const graphProductId = updatedContext.product_id || templateData.sourceDeepDiveId;
|
|
10397
9938
|
if (graphProductId) {
|
|
@@ -10834,7 +10375,6 @@ Raw counts: ${JSON.stringify({
|
|
|
10834
10375
|
if (!product_id) {
|
|
10835
10376
|
throw new McpError(ErrorCode.InvalidParams, "product_id is required");
|
|
10836
10377
|
}
|
|
10837
|
-
initFirebase();
|
|
10838
10378
|
const [entities, edges, constraintNodes, bindings, existingMeta] = await Promise.all([
|
|
10839
10379
|
getAllEntities(product_id),
|
|
10840
10380
|
getAllEdges(product_id),
|
|
@@ -10865,11 +10405,7 @@ Raw counts: ${JSON.stringify({
|
|
|
10865
10405
|
});
|
|
10866
10406
|
await recordScoreSnapshot(product_id, metrics, "metrics_refresh");
|
|
10867
10407
|
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
|
-
}
|
|
10408
|
+
await updatePremortem(product_id, { metrics });
|
|
10873
10409
|
} catch (e) {
|
|
10874
10410
|
console.error("Metrics sync to premortem_jobs failed (non-fatal):", e);
|
|
10875
10411
|
}
|
|
@@ -10890,7 +10426,6 @@ ${JSON.stringify(metrics, null, 2)}` }
|
|
|
10890
10426
|
if (!product_id || !file_path) {
|
|
10891
10427
|
throw new McpError(ErrorCode.InvalidParams, "product_id and file_path are required");
|
|
10892
10428
|
}
|
|
10893
|
-
initFirebase();
|
|
10894
10429
|
const [rgrEntities, rgrEdges, rgrConstraints, rgrBindings] = await Promise.all([
|
|
10895
10430
|
getAllEntities(product_id),
|
|
10896
10431
|
getAllEdges(product_id),
|
|
@@ -10959,7 +10494,6 @@ ${JSON.stringify(metrics, null, 2)}` }
|
|
|
10959
10494
|
if (!validPhases.includes(phase)) {
|
|
10960
10495
|
throw new McpError(ErrorCode.InvalidParams, `Invalid phase "${phase}". Must be one of: ${validPhases.join(", ")}`);
|
|
10961
10496
|
}
|
|
10962
|
-
initFirebase();
|
|
10963
10497
|
const meta = await getGraphMetadata(product_id);
|
|
10964
10498
|
const existing = meta?.rgr_completed_phases ?? [];
|
|
10965
10499
|
if (existing.includes(phase)) {
|
|
@@ -11021,7 +10555,6 @@ ${JSON.stringify(metrics, null, 2)}` }
|
|
|
11021
10555
|
if (!product_id) {
|
|
11022
10556
|
throw new McpError(ErrorCode.InvalidParams, "product_id is required");
|
|
11023
10557
|
}
|
|
11024
|
-
initFirebase();
|
|
11025
10558
|
const [genEntities, genEdges, genConstraints, genBindings] = await Promise.all([
|
|
11026
10559
|
getAllEntities(product_id),
|
|
11027
10560
|
getAllEdges(product_id),
|
|
@@ -11215,9 +10748,11 @@ Meta: ${JSON.stringify({
|
|
|
11215
10748
|
if (!product_id) {
|
|
11216
10749
|
throw new McpError(ErrorCode.InvalidParams, "product_id is required");
|
|
11217
10750
|
}
|
|
11218
|
-
const { report, reportId } = await
|
|
10751
|
+
const { report, reportId } = await generateReadinessReportViaProxy({
|
|
11219
10752
|
productId: product_id,
|
|
11220
|
-
ownerUid: effectiveUid
|
|
10753
|
+
ownerUid: effectiveUid,
|
|
10754
|
+
computeMetricsFromGraph,
|
|
10755
|
+
computeGrade
|
|
11221
10756
|
});
|
|
11222
10757
|
const reportMd = buildReportMarkdown(report, reportId);
|
|
11223
10758
|
const snippets = buildBadgeSnippets(report, reportId);
|
|
@@ -11286,15 +10821,8 @@ Meta: ${JSON.stringify({
|
|
|
11286
10821
|
addEdges: (pid, edges) => addEdges(pid, edges),
|
|
11287
10822
|
upsertBindings: (pid, bindings) => upsertBindings(pid, bindings),
|
|
11288
10823
|
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
10824
|
const runInput = {
|
|
11296
10825
|
mode: "product",
|
|
11297
|
-
productId: docRef.id,
|
|
11298
10826
|
project: {
|
|
11299
10827
|
name: payload.extracted.productName,
|
|
11300
10828
|
brief: payload.extracted.productBrief,
|
|
@@ -11317,16 +10845,16 @@ Meta: ${JSON.stringify({
|
|
|
11317
10845
|
artifacts.target_users = payload.extracted.targetUsers;
|
|
11318
10846
|
if (Object.keys(artifacts).length > 0)
|
|
11319
10847
|
runInput.conversational_artifacts = artifacts;
|
|
11320
|
-
|
|
10848
|
+
const { id: newJobId } = await createPremortem({
|
|
11321
10849
|
status: "queued",
|
|
11322
|
-
payload: runInput,
|
|
11323
|
-
createdAt: admin.firestore.FieldValue.serverTimestamp(),
|
|
11324
|
-
uid: effectiveUid || null,
|
|
10850
|
+
payload: { ...runInput, productId: void 0 },
|
|
11325
10851
|
source: "code_audit",
|
|
11326
10852
|
mode: "product",
|
|
11327
10853
|
codeContext: payload.codeContext || null
|
|
11328
|
-
})
|
|
11329
|
-
|
|
10854
|
+
});
|
|
10855
|
+
runInput.productId = newJobId;
|
|
10856
|
+
await updatePremortem(newJobId, { payload: runInput });
|
|
10857
|
+
return { jobId: newJobId };
|
|
11330
10858
|
}
|
|
11331
10859
|
});
|
|
11332
10860
|
const sections = [
|
|
@@ -11450,11 +10978,7 @@ These constraints will now fire via \`constraints_auto\` when you edit matched f
|
|
|
11450
10978
|
metrics: auditMetrics
|
|
11451
10979
|
});
|
|
11452
10980
|
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
|
-
}
|
|
10981
|
+
await updatePremortem(auditArgs.product_id, { metrics: auditMetrics });
|
|
11458
10982
|
} catch (e) {
|
|
11459
10983
|
console.error("Metrics recompute after code audit failed (non-fatal):", e);
|
|
11460
10984
|
}
|