fullstackgtm 0.47.0 → 0.49.0
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/CHANGELOG.md +76 -6
- package/CONTRIBUTING.md +23 -4
- package/DATA-FLOWS.md +7 -2
- package/INSTALL_FOR_AGENTS.md +15 -6
- package/README.md +28 -9
- package/SECURITY.md +27 -3
- package/dist/audit.js +7 -0
- package/dist/backfill.js +2 -16
- package/dist/cli/audit.js +10 -7
- package/dist/cli/auth.js +8 -4
- package/dist/cli/fix.d.ts +3 -0
- package/dist/cli/fix.js +90 -8
- package/dist/cli/help.d.ts +6 -0
- package/dist/cli/help.js +81 -3
- package/dist/cli/market.js +180 -2
- package/dist/cli/plans.js +56 -5
- package/dist/cli/suggest.d.ts +2 -1
- package/dist/cli/suggest.js +49 -3
- package/dist/cli/ui.js +2 -0
- package/dist/cli.js +41 -16
- package/dist/config.d.ts +13 -1
- package/dist/config.js +23 -2
- package/dist/connectors/hubspot.d.ts +2 -1
- package/dist/connectors/prospectSources.js +4 -11
- package/dist/connectors/salesforce.d.ts +2 -1
- package/dist/connectors/salesforce.js +12 -5
- package/dist/connectors/salesforceAuth.d.ts +9 -0
- package/dist/connectors/salesforceAuth.js +47 -5
- package/dist/connectors/signalSources.js +2 -11
- package/dist/connectors/theirstack.d.ts +0 -19
- package/dist/connectors/theirstack.js +3 -10
- package/dist/credentials.js +36 -26
- package/dist/format.js +31 -5
- package/dist/freeEmailDomains.d.ts +1 -0
- package/dist/freeEmailDomains.js +14 -0
- package/dist/hierarchy.d.ts +29 -0
- package/dist/hierarchy.js +164 -0
- package/dist/index.d.ts +9 -4
- package/dist/index.js +8 -3
- package/dist/market.d.ts +1 -1
- package/dist/market.js +7 -127
- package/dist/marketClassify.d.ts +10 -0
- package/dist/marketClassify.js +52 -1
- package/dist/marketSourcing.js +7 -45
- package/dist/mcp.js +86 -130
- package/dist/planStore.d.ts +28 -1
- package/dist/planStore.js +195 -49
- package/dist/providerError.d.ts +21 -0
- package/dist/providerError.js +30 -0
- package/dist/publicHttp.d.ts +28 -0
- package/dist/publicHttp.js +143 -0
- package/dist/relationships.d.ts +39 -0
- package/dist/relationships.js +101 -0
- package/dist/route.d.ts +46 -0
- package/dist/route.js +228 -0
- package/dist/rules.d.ts +1 -0
- package/dist/rules.js +172 -12
- package/dist/secureFile.d.ts +15 -0
- package/dist/secureFile.js +164 -0
- package/dist/types.d.ts +16 -1
- package/docs/api.md +49 -5
- package/docs/architecture.md +18 -4
- package/llms.txt +7 -0
- package/package.json +5 -3
- package/skills/fullstackgtm/SKILL.md +9 -3
- package/src/audit.ts +7 -0
- package/src/backfill.ts +2 -17
- package/src/cli/audit.ts +10 -7
- package/src/cli/auth.ts +8 -4
- package/src/cli/fix.ts +96 -8
- package/src/cli/help.ts +88 -3
- package/src/cli/market.ts +181 -2
- package/src/cli/plans.ts +66 -5
- package/src/cli/suggest.ts +58 -4
- package/src/cli/ui.ts +1 -0
- package/src/cli.ts +39 -14
- package/src/config.ts +39 -1
- package/src/connectors/hubspot.ts +3 -1
- package/src/connectors/prospectSources.ts +4 -11
- package/src/connectors/salesforce.ts +15 -6
- package/src/connectors/salesforceAuth.ts +46 -6
- package/src/connectors/signalSources.ts +2 -12
- package/src/connectors/theirstack.ts +3 -10
- package/src/credentials.ts +47 -28
- package/src/format.ts +33 -5
- package/src/freeEmailDomains.ts +14 -0
- package/src/hierarchy.ts +185 -0
- package/src/index.ts +29 -0
- package/src/market.ts +7 -111
- package/src/marketClassify.ts +61 -1
- package/src/marketSourcing.ts +7 -43
- package/src/mcp.ts +115 -152
- package/src/planStore.ts +235 -57
- package/src/providerError.ts +37 -0
- package/src/publicHttp.ts +147 -0
- package/src/relationships.ts +115 -0
- package/src/route.ts +278 -0
- package/src/rules.ts +192 -12
- package/src/secureFile.ts +169 -0
- package/src/types.ts +18 -0
package/dist/mcp.js
CHANGED
|
@@ -129,6 +129,9 @@ function packageVersion() {
|
|
|
129
129
|
return "0.0.0";
|
|
130
130
|
}
|
|
131
131
|
}
|
|
132
|
+
function strictSchema(shape) {
|
|
133
|
+
return z.object(shape).strict();
|
|
134
|
+
}
|
|
132
135
|
// Single registration table. startMcpServer() registers exactly this list,
|
|
133
136
|
// and fullstackgtm_capabilities reports its names from the same array — the
|
|
134
137
|
// advertised inventory cannot drift from what is actually registered.
|
|
@@ -143,7 +146,7 @@ const toolDefinitions = [
|
|
|
143
146
|
description: "Run a dry-run GTM hygiene audit and return a reviewable patch plan. " +
|
|
144
147
|
"Sources: the realistic zero-credential demo CRM (provider: \"demo\" — richest test data), " +
|
|
145
148
|
"the minimal sample dataset, a snapshot file, or a live provider.",
|
|
146
|
-
inputSchema: {
|
|
149
|
+
inputSchema: strictSchema({
|
|
147
150
|
provider: z.enum(["sample", "demo", "hubspot", "salesforce", "stripe"]).optional(),
|
|
148
151
|
inputPath: z.string().optional(),
|
|
149
152
|
configPath: z.string().optional(),
|
|
@@ -151,16 +154,23 @@ const toolDefinitions = [
|
|
|
151
154
|
output: z.enum(["json", "markdown"]).optional(),
|
|
152
155
|
today: z.string().optional(),
|
|
153
156
|
staleDealDays: z.number().int().positive().optional(),
|
|
154
|
-
},
|
|
157
|
+
}),
|
|
155
158
|
},
|
|
156
159
|
handler: async ({ provider, inputPath, configPath, rules, output, today, staleDealDays }) => {
|
|
157
160
|
const loaded = configPath ? loadConfig(configPath) : null;
|
|
161
|
+
if (loaded?.config.rulePackages?.length) {
|
|
162
|
+
throw new Error("MCP refuses executable rulePackages from tool-call configPath. " +
|
|
163
|
+
"MCP plugin execution is disabled because mutable tool-call paths are not a durable code-trust boundary. " +
|
|
164
|
+
"Run reviewed plugins through the CLI with --config <path> --allow-plugins instead.");
|
|
165
|
+
}
|
|
158
166
|
const policy = mergePolicy(defaultPolicy(today), loaded?.config);
|
|
159
167
|
if (today)
|
|
160
168
|
policy.today = today;
|
|
161
169
|
if (staleDealDays !== undefined)
|
|
162
170
|
policy.staleDealDays = staleDealDays;
|
|
163
|
-
const ruleSet = await resolveConfiguredRules(loaded
|
|
171
|
+
const ruleSet = await resolveConfiguredRules(loaded, undefined, {
|
|
172
|
+
disableRulePackages: true,
|
|
173
|
+
});
|
|
164
174
|
const selected = rules?.length
|
|
165
175
|
? ruleSet.filter((rule) => rules.includes(rule.id))
|
|
166
176
|
: ruleSet;
|
|
@@ -176,11 +186,11 @@ const toolDefinitions = [
|
|
|
176
186
|
description: "Derive values for a plan's requires_human_* placeholder operations from snapshot " +
|
|
177
187
|
"evidence (account-name matching, contact associations), with confidence levels and " +
|
|
178
188
|
"reasons. Read-only; feed accepted values into fullstackgtm_apply's valueOverrides.",
|
|
179
|
-
inputSchema: {
|
|
189
|
+
inputSchema: strictSchema({
|
|
180
190
|
planPath: z.string(),
|
|
181
191
|
provider: z.enum(["sample", "demo", "hubspot", "salesforce", "stripe"]).optional(),
|
|
182
192
|
inputPath: z.string().optional(),
|
|
183
|
-
},
|
|
193
|
+
}),
|
|
184
194
|
},
|
|
185
195
|
handler: async ({ planPath, provider, inputPath }) => {
|
|
186
196
|
const plan = JSON.parse(readFileSync(resolve(process.cwd(), planPath), "utf8"));
|
|
@@ -198,14 +208,14 @@ const toolDefinitions = [
|
|
|
198
208
|
"uses LLM extraction when an Anthropic/OpenAI key is configured in the server " +
|
|
199
209
|
"environment or credential store, else the free deterministic keyword baseline; " +
|
|
200
210
|
"'llm' and 'deterministic' force either. Read-only; every insight is provenance-marked.",
|
|
201
|
-
inputSchema: {
|
|
211
|
+
inputSchema: strictSchema({
|
|
202
212
|
transcript: z.string().optional(),
|
|
203
213
|
transcriptPath: z.string().optional(),
|
|
204
214
|
title: z.string().optional(),
|
|
205
215
|
source: z.enum(["gong", "chorus", "fathom", "manual", "csv", "unknown"]).optional(),
|
|
206
216
|
extractor: z.enum(["auto", "llm", "deterministic"]).optional(),
|
|
207
217
|
model: z.string().optional(),
|
|
208
|
-
},
|
|
218
|
+
}),
|
|
209
219
|
},
|
|
210
220
|
handler: async ({ transcript, transcriptPath, title, source, extractor, model }) => {
|
|
211
221
|
const raw = transcript ??
|
|
@@ -238,7 +248,7 @@ const toolDefinitions = [
|
|
|
238
248
|
"(exists | ambiguous | safe_to_create) with matches and a reason, using the same " +
|
|
239
249
|
"identity keys as the audit/merge engines (account domain, contact email, open-deal " +
|
|
240
250
|
"key). Read-only. Never create on 'exists' or 'ambiguous'.",
|
|
241
|
-
inputSchema: {
|
|
251
|
+
inputSchema: strictSchema({
|
|
242
252
|
objectType: z.enum(["account", "contact", "deal"]),
|
|
243
253
|
name: z.string().optional(),
|
|
244
254
|
domain: z.string().optional(),
|
|
@@ -246,7 +256,7 @@ const toolDefinitions = [
|
|
|
246
256
|
accountId: z.string().optional(),
|
|
247
257
|
provider: z.enum(["sample", "demo", "hubspot", "salesforce", "stripe"]).optional(),
|
|
248
258
|
inputPath: z.string().optional(),
|
|
249
|
-
},
|
|
259
|
+
}),
|
|
250
260
|
},
|
|
251
261
|
handler: async ({ objectType, name, domain, email, accountId, provider, inputPath }) => {
|
|
252
262
|
const snapshot = await readSnapshot(provider, inputPath);
|
|
@@ -259,7 +269,7 @@ const toolDefinitions = [
|
|
|
259
269
|
config: {
|
|
260
270
|
title: "List Audit Rules",
|
|
261
271
|
description: "List the built-in deterministic audit rules with ids and descriptions.",
|
|
262
|
-
inputSchema: {},
|
|
272
|
+
inputSchema: strictSchema({}),
|
|
263
273
|
},
|
|
264
274
|
handler: async () => {
|
|
265
275
|
return content(builtinAuditRules.map(({ id, title, description }) => ({ id, title, description })));
|
|
@@ -270,122 +280,79 @@ const toolDefinitions = [
|
|
|
270
280
|
writesCrm: true,
|
|
271
281
|
config: {
|
|
272
282
|
title: "Apply Approved Patch Operations",
|
|
273
|
-
description: "Apply
|
|
274
|
-
"
|
|
275
|
-
"
|
|
276
|
-
"
|
|
277
|
-
"the store's HMAC approval digests — ids not approved with `plans approve` are " +
|
|
278
|
-
"refused — and the run is recorded onto the stored plan so `plans show` and " +
|
|
283
|
+
description: "Apply a stored, human-approved patch plan through a provider connector. The plan, " +
|
|
284
|
+
"approved operation ids, and concrete placeholder values are loaded exclusively from " +
|
|
285
|
+
"the local plan store and verified against its HMAC approval digests. Callers cannot " +
|
|
286
|
+
"supply or widen approvals. Every apply run is recorded so `plans show` and " +
|
|
279
287
|
"`audit-log export` include it.",
|
|
280
|
-
inputSchema: {
|
|
288
|
+
inputSchema: strictSchema({
|
|
281
289
|
provider: z.enum(["hubspot", "salesforce"]),
|
|
282
|
-
|
|
283
|
-
approvedOperationIds: z.array(z.string()).min(1),
|
|
284
|
-
valueOverrides: z.record(z.string(), z.string()).optional(),
|
|
290
|
+
planId: z.string().regex(/^[\w.-]+$/),
|
|
285
291
|
output: z.enum(["json", "markdown"]).optional(),
|
|
286
|
-
},
|
|
292
|
+
}),
|
|
287
293
|
},
|
|
288
|
-
handler: async ({ provider,
|
|
289
|
-
// The
|
|
290
|
-
//
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
if (!filePlan || !Array.isArray(filePlan.operations)) {
|
|
294
|
-
throw new Error(`${planPath} is not a patch plan (expected { id, operations: [...] }).`);
|
|
295
|
-
}
|
|
296
|
-
// Governance parity with CLI apply: when this plan id exists in the local
|
|
297
|
-
// plan store, the STORE is the source of truth — the approved-id set must
|
|
298
|
-
// come from `plans approve` (which HMAC-signed each approved op), the
|
|
299
|
-
// signatures are re-verified here, and the run is recorded back onto the
|
|
300
|
-
// plan so audit-log export sees MCP applies exactly like CLI applies.
|
|
294
|
+
handler: async ({ provider, planId, output }) => {
|
|
295
|
+
// The store is the only authority accepted over MCP. In particular, do
|
|
296
|
+
// not accept a plan body/path, approval ids, or value overrides from the
|
|
297
|
+
// agent call: combining proposal and approval in one call defeats the
|
|
298
|
+
// human approval boundary the plan lifecycle is meant to enforce.
|
|
301
299
|
const store = createFilePlanStore();
|
|
302
|
-
const stored =
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
let plan;
|
|
306
|
-
let effectiveOverrides;
|
|
307
|
-
if (stored) {
|
|
308
|
-
if (stored.status !== "approved") {
|
|
309
|
-
throw new Error(`Plan ${filePlan.id} is ${stored.status}; approve operations first with ` +
|
|
310
|
-
`\`fullstackgtm plans approve ${filePlan.id} --operations <ids|all>\`.`);
|
|
311
|
-
}
|
|
312
|
-
// Downgrade guard (same as CLI apply): an approved plan with no
|
|
313
|
-
// signatures either predates 0.26 or had approvalDigests stripped to
|
|
314
|
-
// skip the integrity check. Refuse rather than trust the file.
|
|
315
|
-
if (stored.approvedOperationIds.length > 0 && !stored.approvalDigests) {
|
|
316
|
-
throw new Error(`Refusing to apply plan ${filePlan.id}: it was approved without integrity signatures ` +
|
|
317
|
-
"(approved before 0.26.0, or its signatures were removed). Re-approve it with " +
|
|
318
|
-
`\`fullstackgtm plans approve ${filePlan.id} --operations <ids|all>\`.`);
|
|
319
|
-
}
|
|
320
|
-
// Never widen: every id the tool wants applied must already be
|
|
321
|
-
// store-approved. (A subset is fine — narrowing never writes more.)
|
|
322
|
-
const storeApproved = new Set(stored.approvedOperationIds);
|
|
323
|
-
const unapproved = approvedOperationIds.filter((id) => !storeApproved.has(id));
|
|
324
|
-
if (unapproved.length > 0) {
|
|
325
|
-
throw new Error(`Refusing to apply plan ${filePlan.id}: operation(s) ${unapproved.join(", ")} were never ` +
|
|
326
|
-
"approved in the plan store. Approve them first with " +
|
|
327
|
-
`\`fullstackgtm plans approve ${filePlan.id} --operations <ids>\`.`);
|
|
328
|
-
}
|
|
329
|
-
// Integrity gate: verify against the EFFECTIVE overrides (stored ∪ tool
|
|
330
|
-
// args), so a tool-supplied value that changes what the human approved
|
|
331
|
-
// is treated as tamper, not a live override — what gets written must
|
|
332
|
-
// equal what was signed.
|
|
333
|
-
effectiveOverrides = { ...stored.valueOverrides, ...(valueOverrides ?? {}) };
|
|
334
|
-
const verification = verifyApprovalDigests(stored.plan.operations, stored.approvedOperationIds, effectiveOverrides, stored.approvalDigests);
|
|
335
|
-
if (!verification.ok) {
|
|
336
|
-
const detail = verification.reason === "no_key"
|
|
337
|
-
? "the plan-signing key is missing (was this plan approved on another machine?). Re-approve it here with `fullstackgtm plans approve`."
|
|
338
|
-
: `these operations differ from what was approved: ${verification.tampered.join(", ")}. ` +
|
|
339
|
-
"If you want a different value, set it at approval (`plans approve --value <op>=<v>`) and re-approve; " +
|
|
340
|
-
"otherwise the plan was edited after approval — review and re-approve.";
|
|
341
|
-
throw new Error(`Refusing to apply plan ${filePlan.id}: ${detail}`);
|
|
342
|
-
}
|
|
343
|
-
plan = stored.plan; // apply what was signed, not what the file says now
|
|
300
|
+
const stored = await store.get(planId);
|
|
301
|
+
if (!stored) {
|
|
302
|
+
throw new Error(`No stored plan with id ${planId}. Save it with \`fullstackgtm audit --save\`, then approve it with \`fullstackgtm plans approve\`.`);
|
|
344
303
|
}
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
plan = filePlan;
|
|
349
|
-
const known = new Set(plan.operations.map((operation) => operation.id));
|
|
350
|
-
const unknown = approvedOperationIds.filter((id) => !known.has(id));
|
|
351
|
-
if (unknown.length > 0) {
|
|
352
|
-
throw new Error(`Plan ${planPath} has no operation(s) ${unknown.join(", ")} — approvedOperationIds ` +
|
|
353
|
-
"must reference operations in the plan.");
|
|
354
|
-
}
|
|
355
|
-
effectiveOverrides = valueOverrides ?? {};
|
|
304
|
+
if (stored.status !== "approved") {
|
|
305
|
+
throw new Error(`Plan ${planId} is ${stored.status}; approve operations first with ` +
|
|
306
|
+
`\`fullstackgtm plans approve ${planId} --operations <ids|all>\`.`);
|
|
356
307
|
}
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
valueOverrides: effectiveOverrides,
|
|
360
|
-
});
|
|
361
|
-
// Persist the run (same data the CLI records) so runs[] and plan status
|
|
362
|
-
// update and audit-log export includes MCP applies. External plan files
|
|
363
|
-
// have no store entry to update — say so instead of silently dropping it.
|
|
364
|
-
let runRecorded = false;
|
|
365
|
-
if (stored) {
|
|
366
|
-
await store.recordRun(plan.id, run);
|
|
367
|
-
runRecorded = true;
|
|
308
|
+
if (stored.approvedOperationIds.length === 0) {
|
|
309
|
+
throw new Error(`Plan ${planId} has no approved operations.`);
|
|
368
310
|
}
|
|
311
|
+
if (!stored.approvalDigests) {
|
|
312
|
+
throw new Error(`Refusing to apply plan ${planId}: it was approved without integrity signatures ` +
|
|
313
|
+
"(approved before 0.26.0, or its signatures were removed). Re-approve it with " +
|
|
314
|
+
`\`fullstackgtm plans approve ${planId} --operations <ids|all>\`.`);
|
|
315
|
+
}
|
|
316
|
+
const verification = verifyApprovalDigests(stored.plan.operations, stored.approvedOperationIds, stored.valueOverrides, stored.approvalDigests);
|
|
317
|
+
if (!verification.ok) {
|
|
318
|
+
const detail = verification.reason === "no_key"
|
|
319
|
+
? "the plan-signing key is missing (was this plan approved on another machine?). Re-approve it here with `fullstackgtm plans approve`."
|
|
320
|
+
: `these operations differ from what was approved: ${verification.tampered.join(", ")}. ` +
|
|
321
|
+
"The stored plan or approved values changed after approval — review and re-approve.";
|
|
322
|
+
throw new Error(`Refusing to apply plan ${planId}: ${detail}`);
|
|
323
|
+
}
|
|
324
|
+
const connector = await connectorFor(provider);
|
|
325
|
+
// Claim only after all preflight checks and credential resolution. The
|
|
326
|
+
// atomic store transition prevents a second CLI/MCP process from
|
|
327
|
+
// applying the same approval concurrently.
|
|
328
|
+
const claimed = await store.claimApply(planId, { provider, source: "mcp" });
|
|
329
|
+
const claimedVerification = verifyApprovalDigests(claimed.stored.plan.operations, claimed.stored.approvedOperationIds, claimed.stored.valueOverrides, claimed.stored.approvalDigests);
|
|
330
|
+
if (!claimedVerification.ok) {
|
|
331
|
+
await store.abortApplyPreflight(planId, claimed.claimId, "Approval integrity verification failed after the claim and before provider I/O.");
|
|
332
|
+
throw new Error(`Refusing to apply plan ${planId}: the claimed plan differs from what was approved: ` +
|
|
333
|
+
`${claimedVerification.tampered.join(", ") || "signing key unavailable"}.`);
|
|
334
|
+
}
|
|
335
|
+
let run;
|
|
336
|
+
try {
|
|
337
|
+
run = await applyPatchPlan(connector, claimed.stored.plan, {
|
|
338
|
+
approvedOperationIds: claimed.stored.approvedOperationIds,
|
|
339
|
+
valueOverrides: claimed.stored.valueOverrides,
|
|
340
|
+
});
|
|
341
|
+
}
|
|
342
|
+
catch (error) {
|
|
343
|
+
await store.markApplyUncertain(planId, claimed.claimId);
|
|
344
|
+
throw error;
|
|
345
|
+
}
|
|
346
|
+
await store.recordRun(claimed.stored.plan.id, run, claimed.claimId);
|
|
369
347
|
const governance = {
|
|
370
|
-
planSource:
|
|
371
|
-
approvalDigestsVerified:
|
|
372
|
-
runRecorded,
|
|
373
|
-
...(stored
|
|
374
|
-
? {}
|
|
375
|
-
: {
|
|
376
|
-
note: "Plan file is not in the local plan store: approvals came from tool arguments " +
|
|
377
|
-
"(verified against the plan content only — no approval-digest check) and the run " +
|
|
378
|
-
"was not recorded. Use `fullstackgtm audit --save` + `plans approve` for " +
|
|
379
|
-
"store-backed governance.",
|
|
380
|
-
}),
|
|
348
|
+
planSource: "store",
|
|
349
|
+
approvalDigestsVerified: true,
|
|
350
|
+
runRecorded: true,
|
|
381
351
|
};
|
|
382
352
|
if (output === "markdown") {
|
|
383
353
|
return content(`${formatPatchPlanRun(run)}\n\nGovernance: plan source ${governance.planSource}; ` +
|
|
384
|
-
(
|
|
385
|
-
? "approval digests verified; run recorded on the stored plan (audit-log export includes it)."
|
|
386
|
-
: governance.note));
|
|
354
|
+
"approval digests verified; run recorded on the stored plan (audit-log export includes it).");
|
|
387
355
|
}
|
|
388
|
-
// Backward compatible: the run's own fields stay top-level; governance is additive.
|
|
389
356
|
return content({ ...run, governance });
|
|
390
357
|
},
|
|
391
358
|
},
|
|
@@ -400,11 +367,11 @@ const toolDefinitions = [
|
|
|
400
367
|
"and quote verbatim spans (≤300 chars) for every loud/quiet reading. Submit the full " +
|
|
401
368
|
"ObservationSet via fullstackgtm_market_observe — quotes are verified character-for-" +
|
|
402
369
|
"character against the captures, so never paraphrase.",
|
|
403
|
-
inputSchema: {
|
|
370
|
+
inputSchema: strictSchema({
|
|
404
371
|
vendorId: z.string(),
|
|
405
372
|
configPath: z.string().optional().describe("Path to market.config.json (default ./market.config.json)"),
|
|
406
373
|
captureRun: z.string().optional(),
|
|
407
|
-
},
|
|
374
|
+
}),
|
|
408
375
|
},
|
|
409
376
|
handler: async ({ vendorId, configPath, captureRun }) => {
|
|
410
377
|
const config = loadMarketConfigOrHint(resolve(process.cwd(), configPath ?? "market.config.json"));
|
|
@@ -420,10 +387,10 @@ const toolDefinitions = [
|
|
|
420
387
|
"Validates coverage, the verbatim-evidence rule, and mechanically verifies every quoted " +
|
|
421
388
|
"span against the stored capture it cites. Returns problems if rejected; nothing is " +
|
|
422
389
|
"stored unless the whole set passes. Observations are append-only — use a new runLabel.",
|
|
423
|
-
inputSchema: {
|
|
390
|
+
inputSchema: strictSchema({
|
|
424
391
|
observationsPath: z.string().describe("Path to the ObservationSet JSON file"),
|
|
425
392
|
configPath: z.string().optional().describe("Path to market.config.json (default ./market.config.json)"),
|
|
426
|
-
},
|
|
393
|
+
}),
|
|
427
394
|
},
|
|
428
395
|
handler: async ({ observationsPath, configPath }) => {
|
|
429
396
|
const config = loadMarketConfigOrHint(resolve(process.cwd(), configPath ?? "market.config.json"));
|
|
@@ -454,6 +421,7 @@ toolDefinitions.unshift({
|
|
|
454
421
|
description: "Machine-readable contract for this MCP server: the tool inventory (derived from the " +
|
|
455
422
|
"server's own registration table, with per-tool CRM-write flags), safety defaults, and " +
|
|
456
423
|
"the CLI entrypoints for everything the tools don't cover.",
|
|
424
|
+
inputSchema: strictSchema({}),
|
|
457
425
|
},
|
|
458
426
|
handler: async () => content({
|
|
459
427
|
ok: true,
|
|
@@ -466,7 +434,7 @@ toolDefinitions.unshift({
|
|
|
466
434
|
writesCrm,
|
|
467
435
|
})),
|
|
468
436
|
safety: "Only tools flagged writesCrm can reach a CRM; fullstackgtm_apply writes only operations " +
|
|
469
|
-
"
|
|
437
|
+
"from a stored plan whose ids and values were human-approved and HMAC-verified.",
|
|
470
438
|
server: { command: "npx -y fullstackgtm-mcp" },
|
|
471
439
|
cli: {
|
|
472
440
|
command: "npx fullstackgtm <command> [--json]",
|
|
@@ -493,18 +461,6 @@ export async function startMcpServer() {
|
|
|
493
461
|
const transport = new StdioServerTransport();
|
|
494
462
|
await server.connect(transport);
|
|
495
463
|
}
|
|
496
|
-
/**
|
|
497
|
-
* A plan-store file is a StoredPlan envelope ({ plan, status, runs, ... });
|
|
498
|
-
* `audit --out` writes a bare PatchPlan. fullstackgtm_apply accepts either.
|
|
499
|
-
*/
|
|
500
|
-
function isStoredPlanFile(value) {
|
|
501
|
-
if (typeof value !== "object" || value === null || !("plan" in value))
|
|
502
|
-
return false;
|
|
503
|
-
const plan = value.plan;
|
|
504
|
-
return (typeof plan === "object" &&
|
|
505
|
-
plan !== null &&
|
|
506
|
-
Array.isArray(plan.operations));
|
|
507
|
-
}
|
|
508
464
|
function loadMarketConfigOrHint(path) {
|
|
509
465
|
try {
|
|
510
466
|
return loadMarketConfig(path);
|
package/dist/planStore.d.ts
CHANGED
|
@@ -17,17 +17,44 @@ export type StoredPlan = {
|
|
|
17
17
|
* file is caught instead of written. Absent on plans approved before 0.26.0.
|
|
18
18
|
*/
|
|
19
19
|
approvalDigests?: Record<string, string>;
|
|
20
|
+
/** Monotonic store revision used to make lifecycle changes explicit. */
|
|
21
|
+
revision: number;
|
|
22
|
+
/** Present only while one process owns the right to apply this plan. */
|
|
23
|
+
applyClaim?: {
|
|
24
|
+
id: string;
|
|
25
|
+
claimedAt: string;
|
|
26
|
+
revision: number;
|
|
27
|
+
};
|
|
28
|
+
/** Durable journal of apply ownership. An unresolved entry must never be replayed automatically. */
|
|
29
|
+
applyAttempts?: ApplyAttempt[];
|
|
20
30
|
runs: PatchPlanRun[];
|
|
21
31
|
createdAt: string;
|
|
22
32
|
updatedAt: string;
|
|
23
33
|
};
|
|
34
|
+
export type ApplyAttempt = {
|
|
35
|
+
id: string;
|
|
36
|
+
claimedAt: string;
|
|
37
|
+
provider: string;
|
|
38
|
+
source: "cli" | "fix" | "mcp";
|
|
39
|
+
status: "in_progress" | "preflight_aborted" | "completed" | "uncertain";
|
|
40
|
+
resolvedAt?: string;
|
|
41
|
+
/** Deliberately generic: provider errors can contain CRM data or credentials. */
|
|
42
|
+
note?: string;
|
|
43
|
+
};
|
|
24
44
|
export interface PlanStore {
|
|
25
45
|
save(plan: PatchPlan): Promise<StoredPlan>;
|
|
26
46
|
get(planId: string): Promise<StoredPlan | null>;
|
|
27
47
|
list(status?: ApprovalStatus): Promise<StoredPlan[]>;
|
|
28
48
|
approveOperations(planId: string, operationIds: string[], valueOverrides?: Record<string, unknown>): Promise<StoredPlan>;
|
|
29
49
|
reject(planId: string): Promise<StoredPlan>;
|
|
30
|
-
|
|
50
|
+
claimApply(planId: string, metadata?: Pick<ApplyAttempt, "provider" | "source">): Promise<{
|
|
51
|
+
stored: StoredPlan;
|
|
52
|
+
claimId: string;
|
|
53
|
+
}>;
|
|
54
|
+
recordRun(planId: string, run: PatchPlanRun, claimId: string): Promise<StoredPlan>;
|
|
55
|
+
abortApplyPreflight(planId: string, claimId: string, note: string): Promise<StoredPlan>;
|
|
56
|
+
markApplyUncertain(planId: string, claimId: string): Promise<StoredPlan>;
|
|
57
|
+
recoverApply(planId: string): Promise<StoredPlan>;
|
|
31
58
|
}
|
|
32
59
|
/**
|
|
33
60
|
* Plans as JSON files in a directory (default `$FSGTM_HOME/plans`), one file
|