fullstackgtm 0.48.0 → 0.50.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 +91 -0
- package/CONTRIBUTING.md +23 -4
- package/DATA-FLOWS.md +8 -2
- package/INSTALL_FOR_AGENTS.md +13 -4
- package/README.md +53 -1
- package/SECURITY.md +27 -3
- package/dist/acquireCheckpoint.d.ts +33 -0
- package/dist/acquireCheckpoint.js +149 -0
- package/dist/acquireLinkedIn.d.ts +2 -0
- package/dist/acquireLinkedIn.js +1 -1
- package/dist/cli/audit.js +10 -7
- package/dist/cli/auth.js +8 -4
- package/dist/cli/enrich.js +283 -79
- package/dist/cli/fix.js +20 -7
- package/dist/cli/help.js +23 -15
- package/dist/cli/market.js +180 -2
- package/dist/cli/plans.js +154 -9
- package/dist/cli/shared.d.ts +3 -1
- package/dist/cli/shared.js +2 -2
- package/dist/cli/ui.d.ts +10 -5
- package/dist/cli/ui.js +18 -5
- package/dist/cli.js +1 -1
- package/dist/config.d.ts +13 -1
- package/dist/config.js +23 -2
- package/dist/connectors/linkedin.d.ts +2 -0
- package/dist/connectors/linkedin.js +5 -0
- package/dist/connectors/prospectSources.d.ts +23 -0
- package/dist/connectors/prospectSources.js +25 -14
- 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/theirstack.d.ts +0 -19
- package/dist/connectors/theirstack.js +3 -10
- package/dist/credentials.js +36 -26
- package/dist/enrich.d.ts +44 -1
- package/dist/hostedAcquireCheckpoint.d.ts +43 -0
- package/dist/hostedAcquireCheckpoint.js +129 -0
- package/dist/hostedPatchPlan.d.ts +87 -0
- package/dist/hostedPatchPlan.js +270 -0
- package/dist/icp.js +13 -2
- package/dist/index.d.ts +7 -3
- package/dist/index.js +6 -2
- 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 +67 -115
- package/dist/planStore.d.ts +42 -1
- package/dist/planStore.js +268 -49
- package/dist/progress.d.ts +2 -2
- package/dist/progress.js +2 -2
- 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/secureFile.d.ts +15 -0
- package/dist/secureFile.js +164 -0
- package/dist/types.d.ts +1 -1
- package/docs/api.md +53 -2
- package/docs/architecture.md +22 -2
- package/llms.txt +7 -0
- package/package.json +5 -3
- package/skills/fullstackgtm/SKILL.md +8 -2
- package/src/acquireCheckpoint.ts +186 -0
- package/src/acquireLinkedIn.ts +3 -1
- package/src/cli/audit.ts +10 -7
- package/src/cli/auth.ts +8 -4
- package/src/cli/enrich.ts +322 -78
- package/src/cli/fix.ts +28 -7
- package/src/cli/help.ts +24 -15
- package/src/cli/market.ts +181 -2
- package/src/cli/plans.ts +159 -9
- package/src/cli/shared.ts +2 -1
- package/src/cli/ui.ts +19 -9
- package/src/cli.ts +1 -1
- package/src/config.ts +39 -1
- package/src/connectors/linkedin.ts +6 -0
- package/src/connectors/prospectSources.ts +50 -15
- package/src/connectors/salesforce.ts +12 -5
- package/src/connectors/salesforceAuth.ts +46 -6
- package/src/connectors/theirstack.ts +3 -10
- package/src/credentials.ts +47 -28
- package/src/enrich.ts +47 -1
- package/src/hostedAcquireCheckpoint.ts +156 -0
- package/src/hostedPatchPlan.ts +286 -0
- package/src/icp.ts +14 -2
- package/src/index.ts +24 -0
- package/src/market.ts +7 -111
- package/src/marketClassify.ts +61 -1
- package/src/marketSourcing.ts +7 -43
- package/src/mcp.ts +93 -136
- package/src/planStore.ts +322 -57
- package/src/progress.ts +2 -2
- package/src/providerError.ts +37 -0
- package/src/publicHttp.ts +147 -0
- package/src/secureFile.ts +169 -0
- package/src/types.ts +1 -0
package/src/cli/fix.ts
CHANGED
|
@@ -3,10 +3,11 @@
|
|
|
3
3
|
import { existsSync, readFileSync, writeFileSync } from "node:fs";
|
|
4
4
|
import { resolve } from "node:path";
|
|
5
5
|
import { auditSnapshot, defaultPolicy } from "../audit.ts";
|
|
6
|
-
import { loadConfig, mergePolicy, resolveConfiguredRules } from "../config.ts";
|
|
6
|
+
import { loadConfig, mergePolicy, resolveConfiguredRules, rulePackageTrustFromCli } from "../config.ts";
|
|
7
7
|
import { applyPatchPlan } from "../connector.ts";
|
|
8
8
|
import { patchPlanToMarkdown } from "../format.ts";
|
|
9
9
|
import { createFilePlanStore } from "../planStore.ts";
|
|
10
|
+
import { verifyApprovalDigests } from "../integrity.ts";
|
|
10
11
|
import { resolveRecord, type ResolveCandidate } from "../resolve.ts";
|
|
11
12
|
import { parseAssignmentPolicy } from "../assign.ts";
|
|
12
13
|
import { buildLeadRoutePlan } from "../route.ts";
|
|
@@ -213,8 +214,9 @@ export async function fixCommand(args: string[]) {
|
|
|
213
214
|
}
|
|
214
215
|
const includeCreates = args.includes("--include-creates");
|
|
215
216
|
|
|
216
|
-
const
|
|
217
|
-
const
|
|
217
|
+
const explicitConfig = option(args, "--config") ?? undefined;
|
|
218
|
+
const loaded = loadConfig(explicitConfig);
|
|
219
|
+
const configured = await resolveConfiguredRules(loaded, undefined, rulePackageTrustFromCli(args, explicitConfig));
|
|
218
220
|
const rule = configured.find((candidate) => candidate.id === ruleId);
|
|
219
221
|
if (!rule) {
|
|
220
222
|
throw new Error(`Unknown rule: ${ruleId}. Available rules: ${configured.map((r) => r.id).join(", ")}`);
|
|
@@ -287,6 +289,22 @@ export async function fixCommand(args: string[]) {
|
|
|
287
289
|
return;
|
|
288
290
|
}
|
|
289
291
|
const connector = await connectorFor(provider, args);
|
|
292
|
+
const claimed = await store.claimApply(plan.id, { provider, source: "fix" });
|
|
293
|
+
const { claimId } = claimed;
|
|
294
|
+
const claimedVerification = verifyApprovalDigests(
|
|
295
|
+
claimed.stored.plan.operations,
|
|
296
|
+
claimed.stored.approvedOperationIds,
|
|
297
|
+
claimed.stored.valueOverrides,
|
|
298
|
+
claimed.stored.approvalDigests,
|
|
299
|
+
);
|
|
300
|
+
if (!claimedVerification.ok) {
|
|
301
|
+
await store.abortApplyPreflight(
|
|
302
|
+
plan.id,
|
|
303
|
+
claimId,
|
|
304
|
+
"Approval integrity verification failed after the claim and before provider I/O.",
|
|
305
|
+
);
|
|
306
|
+
throw new Error(`Refusing to apply plan ${plan.id}: approval changed while acquiring the apply claim.`);
|
|
307
|
+
}
|
|
290
308
|
// Live apply board on interactive terminals (stderr; inert otherwise); the
|
|
291
309
|
// same emitter streams heartbeats to the paired hosted app on long runs.
|
|
292
310
|
const renderer = createProgressRenderer(APPLY_STAGES);
|
|
@@ -295,15 +313,18 @@ export async function fixCommand(args: string[]) {
|
|
|
295
313
|
);
|
|
296
314
|
let run: PatchPlanRun;
|
|
297
315
|
try {
|
|
298
|
-
run = await applyPatchPlan(connector, plan, {
|
|
299
|
-
approvedOperationIds:
|
|
300
|
-
valueOverrides:
|
|
316
|
+
run = await applyPatchPlan(connector, claimed.stored.plan, {
|
|
317
|
+
approvedOperationIds: claimed.stored.approvedOperationIds,
|
|
318
|
+
valueOverrides: claimed.stored.valueOverrides,
|
|
301
319
|
progress,
|
|
302
320
|
});
|
|
321
|
+
} catch (error) {
|
|
322
|
+
await store.markApplyUncertain(plan.id, claimId);
|
|
323
|
+
throw error;
|
|
303
324
|
} finally {
|
|
304
325
|
renderer.done();
|
|
305
326
|
}
|
|
306
|
-
await store.recordRun(plan.id, run);
|
|
327
|
+
await store.recordRun(plan.id, run, claimId);
|
|
307
328
|
const counts: Record<string, number> = { applied: 0, conflict: 0, skipped: 0, failed: 0 };
|
|
308
329
|
for (const result of run.results) counts[result.status] = (counts[result.status] ?? 0) + 1;
|
|
309
330
|
lines.push(
|
package/src/cli/help.ts
CHANGED
|
@@ -180,9 +180,10 @@ Usage:
|
|
|
180
180
|
fullstackgtm suggest --plan-id <id> | --plan <path> [source options] [--json] [--out <path>]
|
|
181
181
|
derive values for requires_human_* placeholders
|
|
182
182
|
from snapshot evidence, with confidence + reasons
|
|
183
|
-
fullstackgtm plans list [--status <s>] | show <id> | reject <id>
|
|
183
|
+
fullstackgtm plans list [--status <s>] | show <id> | sync | reject <id>
|
|
184
184
|
fullstackgtm plans approve <id> --operations <ids|all> [--value <opId>=<v>]
|
|
185
185
|
fullstackgtm plans approve <id> --values-from <suggestions.json> [--min-confidence high|low] [--include-creates]
|
|
186
|
+
fullstackgtm plans recover <id> --acknowledge-uncertain-writes
|
|
186
187
|
fullstackgtm apply --plan-id <id> --provider <name>
|
|
187
188
|
fullstackgtm apply --plan-id <id> --channel outbox (render approved openers; transmits nothing)
|
|
188
189
|
fullstackgtm apply --plan <path> --provider <name> --approve <ids|all> [options]
|
|
@@ -203,7 +204,11 @@ Profiles (multi-organization use):
|
|
|
203
204
|
Plan lifecycle:
|
|
204
205
|
audit --save persists the dry-run plan to ~/.fullstackgtm/plans. Approve
|
|
205
206
|
specific operations (optionally with --value <opId>=<v> for placeholders),
|
|
206
|
-
then apply by id — the store enforces approval and records every run.
|
|
207
|
+
then apply by id — the store enforces approval and records every run. When
|
|
208
|
+
paired, local and hosted are replicas of the same immutable plan: either
|
|
209
|
+
capable surface may approve or apply, and each CLI check-in imports missing
|
|
210
|
+
approvals and exact operation receipts. Use \`plans sync\` for an explicit
|
|
211
|
+
check-in; offline changes reconcile later without blocking local work.
|
|
207
212
|
|
|
208
213
|
Authentication (checked in order):
|
|
209
214
|
1. --token-env <name> explicit env var for this invocation (hubspot)
|
|
@@ -230,6 +235,8 @@ Audit options:
|
|
|
230
235
|
--config <path> Config file (default: ./fullstackgtm.config.json if present)
|
|
231
236
|
{ "policy": {...}, "rules": {"enabled":[],"disabled":[]},
|
|
232
237
|
"rulePackages": ["./team-rules.mjs"] }
|
|
238
|
+
--allow-plugins Execute rulePackages from an explicit --config path after review
|
|
239
|
+
--no-plugins Ignore rulePackages; use declarative config and built-in rules only
|
|
233
240
|
--rules <ids> Comma-separated rule ids to run (default: all; see \`rules\`)
|
|
234
241
|
--json Print the JSON patch plan instead of markdown
|
|
235
242
|
--out <path> Also write the JSON patch plan to a file
|
|
@@ -540,14 +547,16 @@ export const HELP: Record<string, HelpEntry> = {
|
|
|
540
547
|
seeAlso: ["audit", "plans", "apply"],
|
|
541
548
|
},
|
|
542
549
|
plans: {
|
|
543
|
-
summary: "plan lifecycle: list / show / approve / reject
|
|
550
|
+
summary: "replicated plan lifecycle: list / show / sync / approve / reject",
|
|
544
551
|
phase: "Govern",
|
|
545
552
|
synopsis: [
|
|
546
|
-
"fullstackgtm plans list [--status <s>] | show <id> | reject <id>",
|
|
553
|
+
"fullstackgtm plans list [--status <s>] | show <id> | sync | reject <id>",
|
|
547
554
|
"fullstackgtm plans approve <id> --operations <ids|all> [--value <opId>=<v>]",
|
|
548
555
|
"fullstackgtm plans approve <id> --values-from <suggestions.json> [--min-confidence high|low]",
|
|
556
|
+
"fullstackgtm plans recover <id> --acknowledge-uncertain-writes",
|
|
549
557
|
],
|
|
550
|
-
detail:
|
|
558
|
+
detail:
|
|
559
|
+
"Approval is explicit and per-operation; placeholders need a concrete --value or a suggestion to be approvable. When paired, hosted and CLI are eventually consistent replicas of one immutable plan. `plans sync` explicitly exchanges approval revisions, execution state, and exact per-operation receipts; ordinary plan commands also check in best-effort. Either surface may apply when its CRM connector supports the selected operations.",
|
|
551
560
|
seeAlso: ["audit", "suggest", "apply"],
|
|
552
561
|
},
|
|
553
562
|
apply: {
|
|
@@ -559,7 +568,7 @@ export const HELP: Record<string, HelpEntry> = {
|
|
|
559
568
|
"fullstackgtm apply --plan <path> --provider <name> --approve <ids|all> [--value <opId>=<v>]",
|
|
560
569
|
],
|
|
561
570
|
detail:
|
|
562
|
-
"The
|
|
571
|
+
"The CLI CRM-write verb. Writes only operations approved locally or imported from the shared hosted replica, with an online execution claim when available, stable operation ids, compare-and-set, resolve-before-create, and readback. Hosted may execute the same synchronized plan when it has a compatible CRM connection; its exact operation receipts are imported on the next CLI check-in. Never writes requires_human_* placeholders without a --value override.",
|
|
563
572
|
seeAlso: ["plans", "suggest", "audit-log"],
|
|
564
573
|
},
|
|
565
574
|
"audit-log": {
|
|
@@ -644,12 +653,12 @@ export const HELP: Record<string, HelpEntry> = {
|
|
|
644
653
|
|
|
645
654
|
// Verbs that print their own richer multi-subcommand help; runCli routes their
|
|
646
655
|
// `--help` to themselves, so commandHelp() only renders these via `help <verb>`.
|
|
647
|
-
export const BESPOKE_HELP = ["init", "call", "market", "tam", "enrich", "
|
|
656
|
+
export const BESPOKE_HELP = ["init", "call", "market", "tam", "enrich", "schedule", "signals", "icp", "draft"];
|
|
648
657
|
|
|
649
658
|
export const GLOBAL_FLAGS = ["--help", "--full"];
|
|
650
659
|
export const GLOBAL_SHORT_FLAGS = ["-h"];
|
|
651
660
|
export const SOURCE_FLAGS = ["--provider", "--token-env", "--input", "--demo", "--sample", "--seed", "--today"];
|
|
652
|
-
export const AUDIT_FLAGS = ["--config", "--rules", "--stale-days", "--fail-on", "--save", "--dry-run", "--json", "--out", "--full"];
|
|
661
|
+
export const AUDIT_FLAGS = ["--config", "--allow-plugins", "--no-plugins", "--rules", "--stale-days", "--fail-on", "--save", "--dry-run", "--json", "--out", "--full"];
|
|
653
662
|
|
|
654
663
|
// Complete per-command flag registry used by runCli's fail-closed flag
|
|
655
664
|
// validation. Keep this next to HELP so focused help, machine capabilities,
|
|
@@ -666,21 +675,21 @@ export const COMMAND_FLAGS: Record<string, string[]> = {
|
|
|
666
675
|
snapshot: [...SOURCE_FLAGS, "--since", "--out", "--archive"],
|
|
667
676
|
audit: [...SOURCE_FLAGS, ...AUDIT_FLAGS],
|
|
668
677
|
report: [...SOURCE_FLAGS, ...AUDIT_FLAGS, "--plan", "--client", "--title", "--prepared-by", "--format", "--max-examples"],
|
|
669
|
-
diff: ["--before", "--after", "--config", "--stale-days", "--today", "--json", "--fail-on-new-findings"],
|
|
670
|
-
rules: ["--config", "--json"],
|
|
678
|
+
diff: ["--before", "--after", "--config", "--allow-plugins", "--no-plugins", "--stale-days", "--today", "--json", "--fail-on-new-findings"],
|
|
679
|
+
rules: ["--config", "--allow-plugins", "--no-plugins", "--json"],
|
|
671
680
|
resolve: [...SOURCE_FLAGS, "--name", "--domain", "--email", "--account-id", "--json"],
|
|
672
681
|
hierarchy: [...SOURCE_FLAGS, "--json", "--out"],
|
|
673
682
|
relationships: [...SOURCE_FLAGS, "--account-id", "--domain", "--json", "--out"],
|
|
674
683
|
route: [...SOURCE_FLAGS, "--match", "--no-inherit-owner", "--reassign-owned", "--policy", "--reason", "--max-operations", "--save", "--dry-run", "--json", "--out"],
|
|
675
|
-
fix: [...SOURCE_FLAGS, "--config", "--rule", "--provider", "--min-confidence", "--include-creates", "--yes", "--confirm", "--dry-run"],
|
|
684
|
+
fix: [...SOURCE_FLAGS, "--config", "--allow-plugins", "--no-plugins", "--rule", "--provider", "--min-confidence", "--include-creates", "--yes", "--confirm", "--dry-run"],
|
|
676
685
|
"bulk-update": [...SOURCE_FLAGS, "--where", "--set", "--guard", "--require", "--create-task", "--archive", "--force-archive-duplicates", "--reason", "--max-operations", "--save", "--dry-run", "--json", "--out"],
|
|
677
686
|
dedupe: [...SOURCE_FLAGS, "--key", "--keep", "--reason", "--max-operations", "--save", "--dry-run", "--json", "--out"],
|
|
678
687
|
reassign: [...SOURCE_FLAGS, "--from", "--assign-unowned", "--to", "--objects", "--where", "--except-deal-stage", "--include-closed-deals", "--reason", "--max-operations", "--save", "--dry-run", "--json", "--out"],
|
|
679
688
|
backfill: [...SOURCE_FLAGS, "--since", "--pipeline", "--match-property", "--skip-unmatched", "--save", "--dry-run", "--json"],
|
|
680
|
-
enrich: [...SOURCE_FLAGS, "--config", "--source", "--icp", "--input", "--provider", "--stale-days", "--assign-owner", "--objects", "--max", "--staged-run", "--run-label", "--label", "--runs", "--save", "--dry-run", "--json", "--out"],
|
|
689
|
+
enrich: [...SOURCE_FLAGS, "--config", "--source", "--icp", "--input", "--provider", "--list", "--stale-days", "--assign-owner", "--objects", "--max", "--scan-limit", "--staged-run", "--run-label", "--label", "--runs", "--save", "--dry-run", "--json", "--out"],
|
|
681
690
|
call: [...SOURCE_FLAGS, "--transcript", "--title", "--source", "--model", "--deterministic", "--heuristics", "--llm", "--json", "--ndjson", "--out", "--call", "--call-type", "--rubric", "--list", "--list-rubrics", "--attendees", "--domain", "--deal", "--save", "--dry-run"],
|
|
682
691
|
suggest: [...SOURCE_FLAGS, "--plan-id", "--plan", "--json", "--out"],
|
|
683
|
-
plans: ["--status", "--operations", "--value", "--values-from", "--min-confidence", "--include-creates", "--json"],
|
|
692
|
+
plans: ["--status", "--operations", "--value", "--values-from", "--min-confidence", "--include-creates", "--acknowledge-uncertain-writes", "--json"],
|
|
684
693
|
apply: ["--plan", "--plan-id", "--provider", "--channel", "--token-env", "--approve", "--value", "--json", "--config"],
|
|
685
694
|
"audit-log": ["--in", "--out", "--json"],
|
|
686
695
|
merge: ["--input", "--out", "--json"],
|
|
@@ -689,11 +698,11 @@ export const COMMAND_FLAGS: Record<string, string[]> = {
|
|
|
689
698
|
icp: [...SOURCE_FLAGS, "--name", "--signals-from", "--with-history", "--prompt", "--min-score", "--from-judge", "--golden", "--against-outcomes", "--min-accuracy", "--model", "--label", "--save", "--json", "--out"],
|
|
690
699
|
signals: ["--bucket", "--source", "--connector", "--connector-opt", "--watchlist", "--keywords", "--from", "--label", "--since", "--account", "--contact", "--unjudged", "--touch", "--result", "--save", "--json", "--explain"],
|
|
691
700
|
draft: ["--from-judge", "--min-score", "--prompt", "--model", "--channel", "--save", "--dry-run", "--json", "--out"],
|
|
692
|
-
schedule: ["--cron", "--label", "--provider", "--trigger", "--runs", "--json"],
|
|
701
|
+
schedule: ["--cron", "--label", "--provider", "--trigger", "--timer", "--runs", "--json"],
|
|
693
702
|
};
|
|
694
703
|
|
|
695
704
|
export const FLAGS_WITH_VALUES = new Set([
|
|
696
|
-
"--account", "--account-id", "--accounts", "--acv", "--acv-basis", "--after", "--anchor", "--api-key", "--approve", "--archive", "--assign-owner", "--attendees", "--before", "--bucket", "--buyers-per-account", "--call", "--call-type", "--calls", "--capture-run", "--category", "--channel", "--client", "--client-id", "--client-secret", "--config", "--connector", "--connector-opt", "--contact", "--cron", "--cross-checks", "--deal", "--deal-period", "--diff", "--domain", "--email", "--except-deal-stage", "--format", "--from", "--from-judge", "--golden", "--guard", "--icp", "--in", "--input", "--instance-url", "--keep", "--key", "--keywords", "--label", "--login-url", "--match", "--match-property", "--max", "--max-claims", "--max-credits", "--max-examples", "--max-operations", "--min-accuracy", "--min-confidence", "--min-mentions", "--min-score", "--model", "--name", "--objects", "--operations", "--out", "--pipeline", "--plan", "--plan-id", "--policy", "--port", "--prepared-by", "--prior-run", "--profile", "--promote-lift", "--prompt", "--provider", "--reason", "--require", "--result", "--rubric", "--rule", "--rules", "--run", "--run-label", "--runs", "--scopes", "--seed", "--set", "--signals-from", "--since", "--snapshot", "--source", "--staged-run", "--stale-days", "--status", "--task-account", "--task-deal", "--title", "--to", "--today", "--token", "--token-env", "--touch", "--transcript", "--trigger", "--usd-per-credit", "--value", "--values-from", "--vendor", "--via", "--watchlist", "--where",
|
|
705
|
+
"--account", "--account-id", "--accounts", "--acv", "--acv-basis", "--after", "--anchor", "--api-key", "--approve", "--archive", "--assign-owner", "--attendees", "--before", "--bucket", "--buyers-per-account", "--call", "--call-type", "--calls", "--capture-run", "--category", "--channel", "--client", "--client-id", "--client-secret", "--config", "--connector", "--connector-opt", "--contact", "--cron", "--cross-checks", "--deal", "--deal-period", "--diff", "--domain", "--email", "--except-deal-stage", "--format", "--from", "--from-judge", "--golden", "--guard", "--icp", "--in", "--input", "--instance-url", "--keep", "--key", "--keywords", "--label", "--list", "--login-url", "--match", "--match-property", "--max", "--max-claims", "--max-credits", "--max-examples", "--max-operations", "--min-accuracy", "--min-confidence", "--min-mentions", "--min-score", "--model", "--name", "--objects", "--operations", "--out", "--pipeline", "--plan", "--plan-id", "--policy", "--port", "--prepared-by", "--prior-run", "--profile", "--promote-lift", "--prompt", "--provider", "--reason", "--require", "--result", "--rubric", "--rule", "--rules", "--run", "--run-label", "--runs", "--scan-limit", "--scopes", "--seed", "--set", "--signals-from", "--since", "--snapshot", "--source", "--staged-run", "--stale-days", "--status", "--task-account", "--task-deal", "--timer", "--title", "--to", "--today", "--token", "--token-env", "--touch", "--transcript", "--trigger", "--usd-per-credit", "--value", "--values-from", "--vendor", "--via", "--watchlist", "--where",
|
|
697
706
|
]);
|
|
698
707
|
|
|
699
708
|
// Lifecycle-grouped front door. One line per verb, organized by the
|
package/src/cli/market.ts
CHANGED
|
@@ -4,7 +4,7 @@ import { existsSync, readFileSync, writeFileSync } from "node:fs";
|
|
|
4
4
|
import { resolve } from "node:path";
|
|
5
5
|
import { createFilePlanStore } from "../planStore.ts";
|
|
6
6
|
import type { ParsedCall } from "../calls.ts";
|
|
7
|
-
import { captureMarket, computeFrontStates, createFileObservationStore, diffFrontStates, loadCaptureTexts, loadMarketConfig, starterMarketConfig, validateObservationSet, verifyEvidenceSpans, type ObservationSet } from "../market.ts";
|
|
7
|
+
import { captureMarket, computeFrontStates, createFileObservationStore, diffFrontStates, loadCaptureTexts, loadMarketConfig, observationId, starterMarketConfig, validateObservationSet, verifyEvidenceSpans, type ObservationSet } from "../market.ts";
|
|
8
8
|
import { assessAxes, axesReportToText } from "../marketAxes.ts";
|
|
9
9
|
import { computeDirectives, computeOverlayStats, directivesToPlan, overlayToMarkdown, type CallDocument } from "../marketOverlay.ts";
|
|
10
10
|
import { computeScaleIndex, scaleReportToText } from "../marketScale.ts";
|
|
@@ -29,6 +29,7 @@ import { unknownSubcommandError } from "./suggest.ts";
|
|
|
29
29
|
*/
|
|
30
30
|
const MARKET_SUBCOMMANDS = [
|
|
31
31
|
"init", "capture", "classify", "worksheet", "observe", "fronts", "axes", "overlay", "scale", "report", "refresh",
|
|
32
|
+
"schema", "hints", "observe-template", "review",
|
|
32
33
|
];
|
|
33
34
|
|
|
34
35
|
export async function marketCommand(args: string[]) {
|
|
@@ -42,9 +43,13 @@ export async function marketCommand(args: string[]) {
|
|
|
42
43
|
market init --category <name> [--out <path>] write a starter market.config.json
|
|
43
44
|
market init --category <name> --auto --vendor <url> [--vendor <url>...] [--anchor <url>] [--max-claims n]
|
|
44
45
|
LLM-propose vendors + claim taxonomy from seed pages (needs an API key)
|
|
46
|
+
market schema print exact MarketConfig + ObservationSet shapes and a tiny example
|
|
45
47
|
market capture [--config <path>] [--run <label>]
|
|
46
48
|
market classify [--run <label>] [--capture-run <label>] [--vendor <id>] [--model m] [--out <path>]
|
|
47
49
|
market worksheet --vendor <id> [--capture-run <label>] [--out <path>]
|
|
50
|
+
market hints [--vendor <id>] [--capture-run <label>] [--json]
|
|
51
|
+
market observe-template [--run <label>] [--out <path>]
|
|
52
|
+
market review --from <observations.json> [--json]
|
|
48
53
|
market observe --from <observations.json|sets.jsonl|spool-dir> [--unverified]
|
|
49
54
|
market fronts [--config <path>] [--run <label>] [--diff <prior-run>] [--json]
|
|
50
55
|
market axes [--config <path>] [--run <label>] [--json]
|
|
@@ -86,6 +91,64 @@ recomputed deterministically on every invocation — never stored.`);
|
|
|
86
91
|
return;
|
|
87
92
|
}
|
|
88
93
|
|
|
94
|
+
if (subcommand === "schema") {
|
|
95
|
+
console.log(`MarketConfig shape:
|
|
96
|
+
{
|
|
97
|
+
"category": "string",
|
|
98
|
+
"anchorVendor": "optional vendor id",
|
|
99
|
+
"vendors": [{
|
|
100
|
+
"id": "stable-vendor-id",
|
|
101
|
+
"name": "Vendor Name",
|
|
102
|
+
"urls": { "home": "https://...", "pricing": null, "product": ["https://..."] },
|
|
103
|
+
"aliases": ["optional alternate names"]
|
|
104
|
+
}],
|
|
105
|
+
"claims": [{
|
|
106
|
+
"id": "stable-claim-id",
|
|
107
|
+
"capability": "Specific differentiating capability",
|
|
108
|
+
"icp": "buyer/segment this claim matters to",
|
|
109
|
+
"pricingStructure": "pricing motion implied by the claim, or unknown",
|
|
110
|
+
"definition": "How to judge LOUD vs QUIET vs ABSENT from page text",
|
|
111
|
+
"terms": ["buyer/search terms for deterministic mention matching"]
|
|
112
|
+
}],
|
|
113
|
+
"surfaceRule": "LOUD = hero/primary positioning; QUIET = supported but secondary; ABSENT = no support; UNOBSERVABLE = no usable capture."
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
ObservationSet shape:
|
|
117
|
+
{
|
|
118
|
+
"id": "set_run1",
|
|
119
|
+
"category": "same category as config",
|
|
120
|
+
"runLabel": "run-1",
|
|
121
|
+
"runAt": "2026-06-11T00:00:00.000Z",
|
|
122
|
+
"extractor": "manual|agent:<model>|llm:<provider>:<model>",
|
|
123
|
+
"observations": [{
|
|
124
|
+
"id": "stable observation id (any unique string is accepted)",
|
|
125
|
+
"vendorId": "vendor id from config",
|
|
126
|
+
"claimId": "claim id from config",
|
|
127
|
+
"observedAt": "2026-06-11",
|
|
128
|
+
"intensity": "loud|quiet|absent|unobservable",
|
|
129
|
+
"confidence": "high|medium|low",
|
|
130
|
+
"reason": "one reviewer-facing sentence",
|
|
131
|
+
"evidence": [{
|
|
132
|
+
"id": "ev1",
|
|
133
|
+
"sourceSystem": "web",
|
|
134
|
+
"sourceObjectType": "page",
|
|
135
|
+
"sourceObjectId": "source URL",
|
|
136
|
+
"text": "VERBATIM quote from captured page text",
|
|
137
|
+
"metadata": { "url": "source URL", "captureHash": "hash from worksheet" }
|
|
138
|
+
}]
|
|
139
|
+
}]
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
Rules:
|
|
143
|
+
- exactly one observation per vendor × claim cell
|
|
144
|
+
- loud/quiet require ≥1 verbatim evidence quote
|
|
145
|
+
- absent/unobservable use [] evidence
|
|
146
|
+
- unobservable means no usable captured page exists for the vendor; if pages were captured but the claim is unsupported, use absent
|
|
147
|
+
- submit with: fullstackgtm market observe --from observations.json
|
|
148
|
+
- derive outputs with: fullstackgtm market fronts --json; fullstackgtm market report --format md --out market-report.md`);
|
|
149
|
+
return;
|
|
150
|
+
}
|
|
151
|
+
|
|
89
152
|
if (subcommand === "init") {
|
|
90
153
|
const category = option(rest, "--category");
|
|
91
154
|
if (!category) throw new Error("market init requires --category <name>");
|
|
@@ -162,7 +225,22 @@ recomputed deterministically on every invocation — never stored.`);
|
|
|
162
225
|
return;
|
|
163
226
|
}
|
|
164
227
|
if (!rest.includes("--unverified")) {
|
|
165
|
-
const { textByHash } = loadCaptureTexts(config.category);
|
|
228
|
+
const { entries, textByHash } = loadCaptureTexts(config.category);
|
|
229
|
+
const usableVendorIds = new Set(
|
|
230
|
+
entries
|
|
231
|
+
.filter((entry) => entry.runLabel === set.runLabel && entry.captureHash && textByHash.has(entry.captureHash))
|
|
232
|
+
.map((entry) => entry.vendorId),
|
|
233
|
+
);
|
|
234
|
+
const unsupportedUnobservable = set.observations.filter((obs) => obs.intensity === "unobservable" && usableVendorIds.has(obs.vendorId));
|
|
235
|
+
if (unsupportedUnobservable.length > 0) {
|
|
236
|
+
console.error(`Rejected: ${unsupportedUnobservable.length} unobservable reading(s) for vendors with usable captures`);
|
|
237
|
+
for (const obs of unsupportedUnobservable.slice(0, 20)) {
|
|
238
|
+
console.error(` - ${obs.vendorId} × ${obs.claimId}: vendor has captured page text; unsupported claims should be absent, not unobservable`);
|
|
239
|
+
}
|
|
240
|
+
console.error("Use unobservable only when no usable captured page exists for that vendor/run. (--unverified skips this gate when captures genuinely live elsewhere.)");
|
|
241
|
+
process.exitCode = 1;
|
|
242
|
+
return;
|
|
243
|
+
}
|
|
166
244
|
const failures = verifyEvidenceSpans(set.observations, textByHash);
|
|
167
245
|
if (failures.length > 0) {
|
|
168
246
|
console.error(`Rejected: ${failures.length} evidence span(s) failed verification against the stored captures`);
|
|
@@ -195,6 +273,107 @@ recomputed deterministically on every invocation — never stored.`);
|
|
|
195
273
|
return;
|
|
196
274
|
}
|
|
197
275
|
|
|
276
|
+
if (subcommand === "hints") {
|
|
277
|
+
const vendorFilter = option(rest, "--vendor");
|
|
278
|
+
const vendorIds = vendorFilter ? [vendorFilter] : config.vendors.map((vendor) => vendor.id);
|
|
279
|
+
const worksheets = vendorIds.map((vendorId) => buildWorksheet(config, vendorId, { captureRun: option(rest, "--capture-run") ?? undefined }));
|
|
280
|
+
if (rest.includes("--json")) {
|
|
281
|
+
console.log(JSON.stringify({
|
|
282
|
+
category: config.category,
|
|
283
|
+
captureRun: worksheets[0]?.captureRun ?? null,
|
|
284
|
+
vendors: worksheets.map((worksheet) => ({ vendor: worksheet.vendor, claimHints: worksheet.claimHints })),
|
|
285
|
+
}, null, 2));
|
|
286
|
+
return;
|
|
287
|
+
}
|
|
288
|
+
for (const worksheet of worksheets) {
|
|
289
|
+
console.log(`\n## ${worksheet.vendor.id} — ${worksheet.vendor.name}`);
|
|
290
|
+
if (worksheet.pages.length === 0) {
|
|
291
|
+
console.log("No usable captures: classify every claim as unobservable.");
|
|
292
|
+
continue;
|
|
293
|
+
}
|
|
294
|
+
for (const hint of worksheet.claimHints) {
|
|
295
|
+
console.log(`\n${hint.claimId}`);
|
|
296
|
+
if (hint.matches.length === 0) {
|
|
297
|
+
console.log(" no lexical matches in captured text (usually absent; still inspect pages for synonyms)");
|
|
298
|
+
continue;
|
|
299
|
+
}
|
|
300
|
+
for (const match of hint.matches) console.log(` - ${match.term} @ ${match.url}: ${match.quote}`);
|
|
301
|
+
}
|
|
302
|
+
}
|
|
303
|
+
return;
|
|
304
|
+
}
|
|
305
|
+
|
|
306
|
+
if (subcommand === "observe-template") {
|
|
307
|
+
const runLabel = option(rest, "--run") ?? "run-1";
|
|
308
|
+
const now = new Date().toISOString();
|
|
309
|
+
const observations = config.vendors.flatMap((vendor) => {
|
|
310
|
+
const worksheet = buildWorksheet(config, vendor.id, { captureRun: runLabel });
|
|
311
|
+
const noPages = worksheet.pages.length === 0;
|
|
312
|
+
return config.claims.map((claim) => ({
|
|
313
|
+
id: observationId(config.category, runLabel, vendor.id, claim.id),
|
|
314
|
+
vendorId: vendor.id,
|
|
315
|
+
claimId: claim.id,
|
|
316
|
+
observedAt: now.slice(0, 10),
|
|
317
|
+
intensity: noPages ? "unobservable" : "absent",
|
|
318
|
+
confidence: "low",
|
|
319
|
+
reason: noPages ? `No usable captures for ${vendor.name}; cannot judge.` : "TODO: inspect worksheet/hints and classify this cell.",
|
|
320
|
+
evidence: [],
|
|
321
|
+
_claimHints: worksheet.claimHints.find((hint) => hint.claimId === claim.id)?.matches ?? [],
|
|
322
|
+
}));
|
|
323
|
+
});
|
|
324
|
+
const set = { id: `set_${runLabel}`, category: config.category, runLabel, runAt: now, extractor: "manual-template", observations };
|
|
325
|
+
const payload = `${JSON.stringify(set, null, 2)}\n`;
|
|
326
|
+
const outPath = option(rest, "--out");
|
|
327
|
+
if (outPath) {
|
|
328
|
+
writeFileSync(resolve(process.cwd(), outPath), payload);
|
|
329
|
+
console.log(`Wrote ${outPath}: ${observations.length} template observations. Fill TODO cells, then run market review --from ${outPath}`);
|
|
330
|
+
} else {
|
|
331
|
+
console.log(payload);
|
|
332
|
+
}
|
|
333
|
+
return;
|
|
334
|
+
}
|
|
335
|
+
|
|
336
|
+
if (subcommand === "review") {
|
|
337
|
+
const fromPath = option(rest, "--from");
|
|
338
|
+
if (!fromPath) throw new Error("market review requires --from <observations.json>");
|
|
339
|
+
const set = JSON.parse(readFileSync(resolve(process.cwd(), fromPath), "utf8")) as ObservationSet;
|
|
340
|
+
const errors = validateObservationSet(config, set).map((detail) => ({ code: "invalid_observation_set", detail }));
|
|
341
|
+
const warnings: Array<{ code: string; detail: string }> = [];
|
|
342
|
+
const { entries, textByHash } = loadCaptureTexts(config.category);
|
|
343
|
+
const usableVendorIds = new Set(
|
|
344
|
+
entries
|
|
345
|
+
.filter((entry) => entry.runLabel === set.runLabel && entry.captureHash && textByHash.has(entry.captureHash))
|
|
346
|
+
.map((entry) => entry.vendorId),
|
|
347
|
+
);
|
|
348
|
+
for (const obs of set.observations) {
|
|
349
|
+
if (obs.intensity === "unobservable" && usableVendorIds.has(obs.vendorId)) {
|
|
350
|
+
errors.push({ code: "unobservable_with_captures", detail: `${obs.vendorId} × ${obs.claimId}: vendor has captured page text; use absent when unsupported.` });
|
|
351
|
+
}
|
|
352
|
+
if ((obs.intensity === "absent" || obs.intensity === "unobservable") && usableVendorIds.has(obs.vendorId)) {
|
|
353
|
+
const hints = buildWorksheet(config, obs.vendorId, { captureRun: set.runLabel }).claimHints.find((hint) => hint.claimId === obs.claimId)?.matches ?? [];
|
|
354
|
+
if (hints.length > 0) {
|
|
355
|
+
warnings.push({
|
|
356
|
+
code: "support_candidate_marked_absent",
|
|
357
|
+
detail: `${obs.vendorId} × ${obs.claimId}: ${hints.length} candidate snippet(s) matched terms but cell is ${obs.intensity}; verify absent vs quiet. Example: ${hints[0].quote}`,
|
|
358
|
+
});
|
|
359
|
+
}
|
|
360
|
+
}
|
|
361
|
+
}
|
|
362
|
+
for (const failure of verifyEvidenceSpans(set.observations, textByHash)) {
|
|
363
|
+
errors.push({ code: "bad_evidence", detail: `${failure.vendorId} × ${failure.claimId}: ${failure.problem}` });
|
|
364
|
+
}
|
|
365
|
+
const summary = { ok: errors.length === 0, errors, warnings };
|
|
366
|
+
if (rest.includes("--json")) {
|
|
367
|
+
console.log(JSON.stringify(summary, null, 2));
|
|
368
|
+
} else {
|
|
369
|
+
console.log(`${summary.ok ? "OK" : "FAILED"}: ${errors.length} error(s), ${warnings.length} warning(s)`);
|
|
370
|
+
for (const err of errors.slice(0, 20)) console.log(`ERROR ${err.code}: ${err.detail}`);
|
|
371
|
+
for (const warn of warnings.slice(0, 20)) console.log(`WARN ${warn.code}: ${warn.detail}`);
|
|
372
|
+
}
|
|
373
|
+
if (errors.length > 0) process.exitCode = 1;
|
|
374
|
+
return;
|
|
375
|
+
}
|
|
376
|
+
|
|
198
377
|
if (subcommand === "classify") {
|
|
199
378
|
const credential = await requireLlmCredential("market classify");
|
|
200
379
|
const vendorFilter = option(rest, "--vendor");
|