@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.
@@ -1,221 +1,88 @@
1
1
  #!/usr/bin/env node
2
2
  import {
3
- GraphTraverser,
4
- computeGenericGraphMetrics,
5
- computeMetricsFromGraph,
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
- validateAuth,
43
- validateRequestSize,
44
- validateSubscription,
45
- withPerfTracking
46
- } from "./chunk-PD2HN2R5.js";
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
- PMJsonSchema,
49
- RunInputSchema,
50
- regenerateAssumptions,
51
- regenerateExperiments,
52
- runRolesAndSummarize,
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 as Buffer2 } from "node: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 = Buffer2.from(p.inlineData.data, "base64").toString("utf8");
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 generateExplorationResponse(message, context);
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
- const storedToken = await getStoredToken();
9054
- if (storedToken) {
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
- initFirebase();
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
- initFirebase();
9150
- const rateLimitApp = getApps4().length > 0 ? getApp4() : void 0;
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 = scanCounter.reset_at?.toDate?.() ?? /* @__PURE__ */ new Date(0);
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 firestoreRetry(() => profileRef.set({ free_scan_counter: { count: 0, reset_at: now } }, { merge: true }), { operationName: "reset_scan_counter" });
9162
- scanCounter.count = 0;
9163
- } else if (scanCounter.count >= 3) {
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
- const { recordScoreSnapshot: recordScoreSnapshot2 } = await import("./score-history-HO5KRVGC.js");
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 app = getApps4().length > 0 ? getApp4() : void 0;
9193
- if (app) {
9194
- const db2 = getFirestore4(app);
9195
- const reportRef = db2.collection("scan_reports").doc();
9196
- reportId = reportRef.id;
9197
- await firestoreRetry(() => reportRef.set({
9198
- uid,
9199
- created_at: admin.firestore.FieldValue.serverTimestamp(),
9200
- metrics: {
9201
- engineering_readiness_pct: result.metrics.engineering_readiness_pct ?? 0,
9202
- security_readiness_pct: result.metrics.security_readiness_pct ?? 0,
9203
- reliability_readiness_pct: result.metrics.reliability_readiness_pct ?? 0,
9204
- scalability_readiness_pct: result.metrics.scalability_readiness_pct ?? 0
9205
- },
9206
- ecosystem: result.ecosystem,
9207
- binding_coverage_pct: result.bindingHealth.coverage_pct,
9208
- frameworks_loaded: result.frameworksLoaded,
9209
- sensitive_data_count: result.sensitiveDataCount,
9210
- findings: result.gatedGapDetails.map((f) => ({
9211
- title: f.title,
9212
- severity: f.severity,
9213
- category: f.category,
9214
- description: f.description
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
- initFirebase();
9241
- const runApp = getApps4().length > 0 ? getApp4() : void 0;
9242
- if (!runApp)
9243
- throw new McpError(ErrorCode.InternalError, "Firebase not initialized");
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
- runRolesAndSummarize(parsedInput, { includeDiagnostics: true }),
8877
+ cfPremortemRun(parsedInput),
9258
8878
  new Promise((_, reject) => setTimeout(() => reject(new Error("Timeout")), timeoutMs))
9259
8879
  ]);
9260
- await firestoreRetry(() => runDocRef.update({ status: "completed", result, finishedAt: admin.firestore.FieldValue.serverTimestamp() }), { operationName: "premortem_run_complete" });
9261
- return { content: [{ type: "text", text: JSON.stringify({ ...result, _jobId: runDocRef.id, _graphProductId: parsedInput.productId }) }] };
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
- initFirebase();
9268
- const ftApp = getApps4().length > 0 ? getApp4() : void 0;
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 ftDocRef = ftDb.collection("premortem_jobs").doc();
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
- createdAt: admin.firestore.FieldValue.serverTimestamp(),
9312
- uid: effectiveUid || null
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
- runRolesAndSummarize(ftInput, { includeDiagnostics: true }),
8927
+ cfPremortemRun(ftInput),
9317
8928
  new Promise((_, reject) => setTimeout(() => reject(new Error("Timeout")), ftTimeoutMs))
9318
8929
  ]);
9319
- await firestoreRetry(() => ftDocRef.update({
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: ftDocRef.id,
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 app = getApps4().length > 0 ? getApp4() : void 0;
9342
- if (!app)
9343
- throw new McpError(ErrorCode.InternalError, "Firebase not initialized");
9344
- const db2 = getFirestore4(app);
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 app = getApps4().length > 0 ? getApp4() : void 0;
9360
- if (!app)
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 app = getApps4().length > 0 ? getApp4() : void 0;
9383
- if (!app)
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 = ref.id;
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
- runRolesAndSummarizeIncremental(kickInput, update),
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 firestoreRetry(() => ref.update({ status: "completed", result: doc, progress: 1, finishedAt: admin.firestore.FieldValue.serverTimestamp() }), { operationName: "premortem_kick_complete" });
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 app = getApps4().length > 0 ? getApp4() : void 0;
9413
- if (!app)
9414
- throw new McpError(ErrorCode.InternalError, "Firebase not initialized");
9415
- const db2 = getFirestore4(app);
9416
- const snapshot = await db2.collection("premortem_jobs").where("uid", "==", effectiveUid).orderBy("createdAt", "desc").limit(limit).get();
9417
- const premortems = snapshot.docs.map((doc) => ({
9418
- id: doc.id,
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, store = true } = args;
9004
+ const { doc } = args;
9428
9005
  const parsedDoc = PMJsonSchema.parse(doc);
9429
- const bytes = await buildPdfBuffer(parsedDoc);
9430
- if (store) {
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 generateAnswer(question, doc);
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 regenerateAssumptions(input, doc);
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 regenerateExperiments(input, doc);
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(effectiveUid, productId);
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(effectiveUid, personaId);
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 chatWithPersona(persona, userMessage, product, conversationHistory);
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 getWikiMarkdown(projectId);
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 saveWikiMarkdown(projectId, markdown, effectiveUid);
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 applyEditsLogic(edits);
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 generateChatSuggestion(prompt, wikiMarkdown);
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 createLinearIssues(schema_json, limit);
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 app = getApps4().length > 0 ? getApp4() : void 0;
9549
- if (!app)
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(effectiveUid, persona_id);
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 getWikiMarkdown(wiki_id);
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
- initFirebase();
10189
- const app = getApps4().length > 0 ? getApp4() : void 0;
10190
- if (!app)
10191
- throw new McpError(ErrorCode.InternalError, "Firebase not initialized");
10192
- const db2 = getFirestore4(app);
10193
- const snapshot = await db2.collection("templates").where("uid", "==", effectiveUid).limit(100).get();
10194
- let templates = snapshot.docs.map((doc) => ({
10195
- id: doc.id,
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
- initFirebase();
10221
- const app = getApps4().length > 0 ? getApp4() : void 0;
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: doc.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?.toDate?.()?.toISOString() || null,
10244
- updatedAt: data.updatedAt?.toDate?.()?.toISOString() || null
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
- initFirebase();
10256
- const app = getApps4().length > 0 ? getApp4() : void 0;
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 firestoreRetry(() => db2.collection("templates").doc(templateId).set({
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
- createdAt: now,
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
- initFirebase();
10315
- const app = getApps4().length > 0 ? getApp4() : void 0;
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 ddDoc = await db2.collection("premortem_jobs").doc(templateData.sourceDeepDiveId).get();
10342
- if (ddDoc.exists && ddDoc.data()?.result) {
10343
- deepDiveData = ddDoc.data()?.result;
9866
+ const ddData = await getPremortem(templateData.sourceDeepDiveId);
9867
+ if (ddData?.result) {
9868
+ deepDiveData = ddData.result;
10344
9869
  }
10345
9870
  }
10346
- const response = await generateTemplateResponse(message, {
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 firestoreRetry(() => db2.collection("templates").doc(template_id).update({
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
- updatedAt: /* @__PURE__ */ new Date()
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
- const metricsApp = getApps4().length > 0 ? getApp4() : void 0;
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 generateReadinessReport({
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
- await firestoreRetry(() => docRef.set({
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
- }), { operationName: "code_audit_create_job" });
11329
- return { jobId: docRef.id };
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
- const auditApp = getApps4().length > 0 ? getApp4() : void 0;
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
  }