sverklo 0.28.2 → 0.29.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/README.md +6 -2
- package/dist/bin/sverklo.js +166 -4
- package/dist/bin/sverklo.js.map +1 -1
- package/dist/src/indexer/parser-tree-sitter.js +0 -1
- package/dist/src/indexer/parser-tree-sitter.js.map +1 -1
- package/dist/src/init-global.js +25 -24
- package/dist/src/init-global.js.map +1 -1
- package/dist/src/init.js +1 -0
- package/dist/src/init.js.map +1 -1
- package/dist/src/marketing/campaign-cycle.d.ts +13 -0
- package/dist/src/marketing/campaign-cycle.js +107 -0
- package/dist/src/marketing/campaign-cycle.js.map +1 -0
- package/dist/src/marketing/content-quality.d.ts +11 -0
- package/dist/src/marketing/content-quality.js +51 -0
- package/dist/src/marketing/content-quality.js.map +1 -0
- package/dist/src/marketing/content-seeding.d.ts +2 -0
- package/dist/src/marketing/content-seeding.js +105 -0
- package/dist/src/marketing/content-seeding.js.map +1 -0
- package/dist/src/marketing/decisions.d.ts +10 -0
- package/dist/src/marketing/decisions.js +142 -0
- package/dist/src/marketing/decisions.js.map +1 -0
- package/dist/src/marketing/index.d.ts +12 -0
- package/dist/src/marketing/index.js +13 -0
- package/dist/src/marketing/index.js.map +1 -0
- package/dist/src/marketing/models.d.ts +189 -0
- package/dist/src/marketing/models.js +5 -0
- package/dist/src/marketing/models.js.map +1 -0
- package/dist/src/marketing/opportunity-scout.d.ts +8 -0
- package/dist/src/marketing/opportunity-scout.js +73 -0
- package/dist/src/marketing/opportunity-scout.js.map +1 -0
- package/dist/src/marketing/profile-health.d.ts +2 -0
- package/dist/src/marketing/profile-health.js +96 -0
- package/dist/src/marketing/profile-health.js.map +1 -0
- package/dist/src/marketing/report.d.ts +4 -0
- package/dist/src/marketing/report.js +67 -0
- package/dist/src/marketing/report.js.map +1 -0
- package/dist/src/marketing/scoring.d.ts +5 -0
- package/dist/src/marketing/scoring.js +89 -0
- package/dist/src/marketing/scoring.js.map +1 -0
- package/dist/src/marketing/status.d.ts +3 -0
- package/dist/src/marketing/status.js +58 -0
- package/dist/src/marketing/status.js.map +1 -0
- package/dist/src/marketing/storage.d.ts +29 -0
- package/dist/src/marketing/storage.js +99 -0
- package/dist/src/marketing/storage.js.map +1 -0
- package/dist/src/marketing/test-fixtures.d.ts +7 -0
- package/dist/src/marketing/test-fixtures.js +31 -0
- package/dist/src/marketing/test-fixtures.js.map +1 -0
- package/dist/src/marketing/validation.d.ts +9 -0
- package/dist/src/marketing/validation.js +66 -0
- package/dist/src/marketing/validation.js.map +1 -0
- package/dist/src/prove.d.ts +6 -2
- package/dist/src/prove.js +119 -24
- package/dist/src/prove.js.map +1 -1
- package/dist/src/server/tools/review-diff.js +0 -1
- package/dist/src/server/tools/review-diff.js.map +1 -1
- package/dist/src/utils/logger.js +2 -0
- package/dist/src/utils/logger.js.map +1 -1
- package/package.json +3 -1
|
@@ -0,0 +1,142 @@
|
|
|
1
|
+
import { assertDecision } from "./validation.js";
|
|
2
|
+
export function createOperatorDecision(input) {
|
|
3
|
+
const now = input.now ?? new Date().toISOString();
|
|
4
|
+
return {
|
|
5
|
+
decision_id: `decision-${now.replace(/[^0-9]/g, "").slice(0, 14)}-${input.targetId}`,
|
|
6
|
+
target_type: input.targetType,
|
|
7
|
+
target_id: input.targetId,
|
|
8
|
+
decision: input.decision,
|
|
9
|
+
reason: input.reason,
|
|
10
|
+
created_at: now,
|
|
11
|
+
applies_to_future_cycles: Boolean(input.future),
|
|
12
|
+
};
|
|
13
|
+
}
|
|
14
|
+
export function applyOperatorDecision(workspace, cycle, decision) {
|
|
15
|
+
assertDecision(decision);
|
|
16
|
+
const changed = applyToTarget(cycle, decision);
|
|
17
|
+
if (!changed)
|
|
18
|
+
throw new Error(`decision target not found: ${decision.target_type} ${decision.target_id}`);
|
|
19
|
+
cycle.decisions.push(decision);
|
|
20
|
+
if (decision.applies_to_future_cycles && decision.reason && shouldBlockFuture(decision.decision)) {
|
|
21
|
+
const normalized = decision.reason.trim();
|
|
22
|
+
if (normalized && !workspace.blocked_topics.some((topic) => topic.toLowerCase() === normalized.toLowerCase())) {
|
|
23
|
+
workspace.blocked_topics.push(normalized);
|
|
24
|
+
}
|
|
25
|
+
}
|
|
26
|
+
const now = decision.created_at;
|
|
27
|
+
workspace.updated_at = now;
|
|
28
|
+
cycle.updated_at = now;
|
|
29
|
+
}
|
|
30
|
+
function shouldBlockFuture(decision) {
|
|
31
|
+
return decision === "reject" || decision === "request_revision";
|
|
32
|
+
}
|
|
33
|
+
function applyToTarget(cycle, decision) {
|
|
34
|
+
if (decision.target_type === "opportunity") {
|
|
35
|
+
const opportunity = cycle.opportunities.find((item) => item.opportunity_id === decision.target_id);
|
|
36
|
+
if (!opportunity)
|
|
37
|
+
return false;
|
|
38
|
+
if (decision.decision === "approve")
|
|
39
|
+
opportunity.status = "approved";
|
|
40
|
+
else if (decision.decision === "reject")
|
|
41
|
+
opportunity.status = "rejected";
|
|
42
|
+
else if (decision.decision === "archive")
|
|
43
|
+
opportunity.status = "archived";
|
|
44
|
+
else if (decision.decision === "request_revision")
|
|
45
|
+
opportunity.status = "needs_review";
|
|
46
|
+
else
|
|
47
|
+
throw new Error(`invalid decision for opportunity: ${decision.decision}`);
|
|
48
|
+
const brief = cycle.briefs.find((item) => item.opportunity_id === opportunity.opportunity_id);
|
|
49
|
+
if (brief && decision.decision === "approve")
|
|
50
|
+
brief.approval_status = "approved";
|
|
51
|
+
if (brief && decision.decision === "reject")
|
|
52
|
+
brief.approval_status = "rejected";
|
|
53
|
+
if (brief && decision.decision === "request_revision")
|
|
54
|
+
brief.approval_status = "revision_requested";
|
|
55
|
+
return true;
|
|
56
|
+
}
|
|
57
|
+
if (decision.target_type === "brief") {
|
|
58
|
+
const brief = cycle.briefs.find((item) => item.brief_id === decision.target_id);
|
|
59
|
+
if (!brief)
|
|
60
|
+
return false;
|
|
61
|
+
if (decision.decision === "approve")
|
|
62
|
+
brief.approval_status = "approved";
|
|
63
|
+
else if (decision.decision === "reject")
|
|
64
|
+
brief.approval_status = "rejected";
|
|
65
|
+
else if (decision.decision === "request_revision")
|
|
66
|
+
brief.approval_status = "revision_requested";
|
|
67
|
+
else
|
|
68
|
+
throw new Error(`invalid decision for brief: ${decision.decision}`);
|
|
69
|
+
return true;
|
|
70
|
+
}
|
|
71
|
+
if (decision.target_type === "content_item") {
|
|
72
|
+
const content = cycle.content_queue?.items.find((item) => item.content_id === decision.target_id);
|
|
73
|
+
if (!content)
|
|
74
|
+
return false;
|
|
75
|
+
applyContentDecision(content, decision);
|
|
76
|
+
return true;
|
|
77
|
+
}
|
|
78
|
+
if (decision.target_type === "profile_recommendation") {
|
|
79
|
+
const recommendation = cycle.profile_report?.recommendations.find((item) => item.recommendation_id === decision.target_id);
|
|
80
|
+
if (!recommendation)
|
|
81
|
+
return false;
|
|
82
|
+
applyProfileDecision(recommendation, decision);
|
|
83
|
+
return true;
|
|
84
|
+
}
|
|
85
|
+
if (decision.target_type === "campaign_cycle") {
|
|
86
|
+
if (cycle.cycle_id !== decision.target_id)
|
|
87
|
+
return false;
|
|
88
|
+
if (decision.decision === "archive")
|
|
89
|
+
cycle.status = "closed";
|
|
90
|
+
else if (decision.decision === "approve")
|
|
91
|
+
cycle.status = "ready";
|
|
92
|
+
else if (decision.decision === "request_revision")
|
|
93
|
+
cycle.status = "operator_review";
|
|
94
|
+
else
|
|
95
|
+
throw new Error(`invalid decision for campaign cycle: ${decision.decision}`);
|
|
96
|
+
return true;
|
|
97
|
+
}
|
|
98
|
+
return false;
|
|
99
|
+
}
|
|
100
|
+
function applyContentDecision(content, decision) {
|
|
101
|
+
if (decision.decision === "approve") {
|
|
102
|
+
if (content.quality_status !== "passed") {
|
|
103
|
+
throw new Error(`content item ${content.content_id} cannot be approved until quality checks pass`);
|
|
104
|
+
}
|
|
105
|
+
content.approval_status = "approved";
|
|
106
|
+
}
|
|
107
|
+
else if (decision.decision === "reject") {
|
|
108
|
+
content.approval_status = "rejected";
|
|
109
|
+
}
|
|
110
|
+
else if (decision.decision === "request_revision") {
|
|
111
|
+
content.approval_status = "needs_revision";
|
|
112
|
+
}
|
|
113
|
+
else if (decision.decision === "record_published") {
|
|
114
|
+
if (content.approval_status !== "scheduled" && content.approval_status !== "approved") {
|
|
115
|
+
throw new Error(`content item ${content.content_id} must be approved or scheduled before publish`);
|
|
116
|
+
}
|
|
117
|
+
content.approval_status = "published";
|
|
118
|
+
}
|
|
119
|
+
else if (decision.decision === "archive") {
|
|
120
|
+
content.approval_status = "rejected";
|
|
121
|
+
}
|
|
122
|
+
else {
|
|
123
|
+
throw new Error(`invalid decision for content item: ${decision.decision}`);
|
|
124
|
+
}
|
|
125
|
+
}
|
|
126
|
+
function applyProfileDecision(recommendation, decision) {
|
|
127
|
+
if (decision.decision === "approve")
|
|
128
|
+
recommendation.status = "accepted";
|
|
129
|
+
else if (decision.decision === "reject")
|
|
130
|
+
recommendation.status = "rejected";
|
|
131
|
+
else if (decision.decision === "record_applied") {
|
|
132
|
+
if (recommendation.status !== "accepted") {
|
|
133
|
+
throw new Error(`profile recommendation ${recommendation.recommendation_id} must be accepted before applied`);
|
|
134
|
+
}
|
|
135
|
+
recommendation.status = "applied";
|
|
136
|
+
}
|
|
137
|
+
else if (decision.decision === "archive")
|
|
138
|
+
recommendation.status = "superseded";
|
|
139
|
+
else
|
|
140
|
+
throw new Error(`invalid decision for profile recommendation: ${decision.decision}`);
|
|
141
|
+
}
|
|
142
|
+
//# sourceMappingURL=decisions.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"decisions.js","sourceRoot":"","sources":["../../../src/marketing/decisions.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
export * from "./models.js";
|
|
2
|
+
export * from "./storage.js";
|
|
3
|
+
export * from "./validation.js";
|
|
4
|
+
export * from "./decisions.js";
|
|
5
|
+
export * from "./scoring.js";
|
|
6
|
+
export * from "./opportunity-scout.js";
|
|
7
|
+
export * from "./content-quality.js";
|
|
8
|
+
export * from "./content-seeding.js";
|
|
9
|
+
export * from "./profile-health.js";
|
|
10
|
+
export * from "./campaign-cycle.js";
|
|
11
|
+
export * from "./report.js";
|
|
12
|
+
export * from "./status.js";
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
export * from "./models.js";
|
|
2
|
+
export * from "./storage.js";
|
|
3
|
+
export * from "./validation.js";
|
|
4
|
+
export * from "./decisions.js";
|
|
5
|
+
export * from "./scoring.js";
|
|
6
|
+
export * from "./opportunity-scout.js";
|
|
7
|
+
export * from "./content-quality.js";
|
|
8
|
+
export * from "./content-seeding.js";
|
|
9
|
+
export * from "./profile-health.js";
|
|
10
|
+
export * from "./campaign-cycle.js";
|
|
11
|
+
export * from "./report.js";
|
|
12
|
+
export * from "./status.js";
|
|
13
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../../src/marketing/index.ts"],"names":[],"mappings":"AAAA,cAAc,aAAa,CAAC;AAC5B,cAAc,cAAc,CAAC;AAC7B,cAAc,iBAAiB,CAAC;AAChC,cAAc,gBAAgB,CAAC;AAC/B,cAAc,cAAc,CAAC;AAC7B,cAAc,wBAAwB,CAAC;AACvC,cAAc,sBAAsB,CAAC;AACrC,cAAc,sBAAsB,CAAC;AACrC,cAAc,qBAAqB,CAAC;AACpC,cAAc,qBAAqB,CAAC;AACpC,cAAc,aAAa,CAAC;AAC5B,cAAc,aAAa,CAAC"}
|
|
@@ -0,0 +1,189 @@
|
|
|
1
|
+
export type ScoreLevel = "low" | "medium" | "high";
|
|
2
|
+
export type CampaignStatus = "planned" | "collecting_inputs" | "agent_review" | "operator_review" | "ready" | "closed";
|
|
3
|
+
export type OpportunityStatus = "discovered" | "rejected" | "needs_review" | "briefed" | "approved" | "archived";
|
|
4
|
+
export type BriefApprovalStatus = "pending" | "approved" | "rejected" | "revision_requested";
|
|
5
|
+
export type ContentType = "single_post" | "thread" | "reply_suggestion" | "profile_pin_candidate";
|
|
6
|
+
export type ContentQualityStatus = "unchecked" | "passed" | "needs_revision" | "blocked";
|
|
7
|
+
export type ContentApprovalStatus = "draft" | "needs_revision" | "approved" | "scheduled" | "published" | "rejected";
|
|
8
|
+
export type ProfileArea = "bio" | "display_name" | "visuals" | "pinned_content" | "link" | "cadence" | "credibility_signal";
|
|
9
|
+
export type Priority = "low" | "medium" | "high" | "critical";
|
|
10
|
+
export type ProfileRecommendationStatus = "proposed" | "accepted" | "rejected" | "applied" | "superseded";
|
|
11
|
+
export type DecisionTargetType = "opportunity" | "brief" | "content_item" | "profile_recommendation" | "campaign_cycle";
|
|
12
|
+
export type DecisionValue = "approve" | "reject" | "request_revision" | "record_published" | "record_applied" | "archive";
|
|
13
|
+
export type EvidenceSourceType = "benchmark" | "release_note" | "readme" | "blog_post" | "manual_approval";
|
|
14
|
+
export interface CampaignWorkspace {
|
|
15
|
+
workspace_id: string;
|
|
16
|
+
account_handle: string;
|
|
17
|
+
active_cycle_id?: string;
|
|
18
|
+
positioning_phrases: string[];
|
|
19
|
+
blocked_topics: string[];
|
|
20
|
+
created_at: string;
|
|
21
|
+
updated_at: string;
|
|
22
|
+
}
|
|
23
|
+
export interface CampaignCycle {
|
|
24
|
+
cycle_id: string;
|
|
25
|
+
status: CampaignStatus;
|
|
26
|
+
period_start: string;
|
|
27
|
+
period_end: string;
|
|
28
|
+
opportunity_ids: string[];
|
|
29
|
+
content_item_ids: string[];
|
|
30
|
+
profile_report_id?: string;
|
|
31
|
+
top_blocker?: string;
|
|
32
|
+
opportunities: MarketingOpportunity[];
|
|
33
|
+
briefs: OpportunityBrief[];
|
|
34
|
+
content_queue?: ContentQueue;
|
|
35
|
+
profile_report?: ProfileHealthReport;
|
|
36
|
+
decisions: OperatorDecision[];
|
|
37
|
+
created_at: string;
|
|
38
|
+
updated_at: string;
|
|
39
|
+
}
|
|
40
|
+
export interface TrendSnapshotItem {
|
|
41
|
+
id: string;
|
|
42
|
+
text: string;
|
|
43
|
+
source_context: string;
|
|
44
|
+
observed_at?: string;
|
|
45
|
+
}
|
|
46
|
+
export interface TrendSnapshot {
|
|
47
|
+
snapshot_id?: string;
|
|
48
|
+
captured_at: string;
|
|
49
|
+
source_label: string;
|
|
50
|
+
source_notes?: string;
|
|
51
|
+
items: TrendSnapshotItem[];
|
|
52
|
+
}
|
|
53
|
+
export interface RecentPost {
|
|
54
|
+
id: string;
|
|
55
|
+
text: string;
|
|
56
|
+
posted_at: string;
|
|
57
|
+
theme?: string;
|
|
58
|
+
}
|
|
59
|
+
export interface RecentPostsSnapshot {
|
|
60
|
+
captured_at: string;
|
|
61
|
+
account_handle?: string;
|
|
62
|
+
posts: RecentPost[];
|
|
63
|
+
}
|
|
64
|
+
export interface ProfileSnapshot {
|
|
65
|
+
captured_at: string;
|
|
66
|
+
account_handle: string;
|
|
67
|
+
display_name: string;
|
|
68
|
+
bio: string;
|
|
69
|
+
pinned_post?: string;
|
|
70
|
+
pinned_post_at?: string;
|
|
71
|
+
profile_link?: string;
|
|
72
|
+
visual_notes?: string;
|
|
73
|
+
}
|
|
74
|
+
export interface OpportunityScore {
|
|
75
|
+
audience_fit: ScoreLevel;
|
|
76
|
+
sverklo_relevance: ScoreLevel;
|
|
77
|
+
urgency: ScoreLevel;
|
|
78
|
+
novelty: ScoreLevel;
|
|
79
|
+
credibility_impact: ScoreLevel;
|
|
80
|
+
brand_safety_risk: ScoreLevel;
|
|
81
|
+
total: number;
|
|
82
|
+
reasons: string[];
|
|
83
|
+
}
|
|
84
|
+
export interface MarketingOpportunity {
|
|
85
|
+
opportunity_id: string;
|
|
86
|
+
source_item_id: string;
|
|
87
|
+
topic: string;
|
|
88
|
+
source_context: string;
|
|
89
|
+
audience_fit: ScoreLevel;
|
|
90
|
+
sverklo_relevance: ScoreLevel;
|
|
91
|
+
urgency: ScoreLevel;
|
|
92
|
+
novelty: ScoreLevel;
|
|
93
|
+
credibility_impact: ScoreLevel;
|
|
94
|
+
brand_safety_risk: ScoreLevel;
|
|
95
|
+
score: number;
|
|
96
|
+
recommended_angle: string;
|
|
97
|
+
status: OpportunityStatus;
|
|
98
|
+
blockers: string[];
|
|
99
|
+
rationale: string[];
|
|
100
|
+
}
|
|
101
|
+
export interface OpportunityBrief {
|
|
102
|
+
brief_id: string;
|
|
103
|
+
opportunity_id: string;
|
|
104
|
+
summary: string;
|
|
105
|
+
why_now: string;
|
|
106
|
+
target_audience: string;
|
|
107
|
+
message_angle: string;
|
|
108
|
+
risk_flags: string[];
|
|
109
|
+
approval_status: BriefApprovalStatus;
|
|
110
|
+
}
|
|
111
|
+
export interface SeedContentItem {
|
|
112
|
+
content_id: string;
|
|
113
|
+
content_type: ContentType;
|
|
114
|
+
text: string;
|
|
115
|
+
goal: string;
|
|
116
|
+
theme: string;
|
|
117
|
+
source_opportunity_id?: string;
|
|
118
|
+
intended_audience: string;
|
|
119
|
+
quality_status: ContentQualityStatus;
|
|
120
|
+
approval_status: ContentApprovalStatus;
|
|
121
|
+
scheduled_for?: string;
|
|
122
|
+
evidence_refs: string[];
|
|
123
|
+
blockers: string[];
|
|
124
|
+
}
|
|
125
|
+
export interface ContentQueue {
|
|
126
|
+
queue_id: string;
|
|
127
|
+
cycle_id: string;
|
|
128
|
+
items: SeedContentItem[];
|
|
129
|
+
coverage_days: number;
|
|
130
|
+
gaps: string[];
|
|
131
|
+
}
|
|
132
|
+
export interface ProfileHealthReport {
|
|
133
|
+
report_id: string;
|
|
134
|
+
cycle_id: string;
|
|
135
|
+
score: number;
|
|
136
|
+
clarity_score: number;
|
|
137
|
+
credibility_score: number;
|
|
138
|
+
alignment_score: number;
|
|
139
|
+
cadence_score: number;
|
|
140
|
+
recommendations: ProfileRecommendation[];
|
|
141
|
+
critical_gaps: string[];
|
|
142
|
+
}
|
|
143
|
+
export interface ProfileRecommendation {
|
|
144
|
+
recommendation_id: string;
|
|
145
|
+
report_id: string;
|
|
146
|
+
profile_area: ProfileArea;
|
|
147
|
+
recommendation: string;
|
|
148
|
+
reason: string;
|
|
149
|
+
expected_visitor_impact: string;
|
|
150
|
+
priority: Priority;
|
|
151
|
+
status: ProfileRecommendationStatus;
|
|
152
|
+
}
|
|
153
|
+
export interface OperatorDecision {
|
|
154
|
+
decision_id: string;
|
|
155
|
+
target_type: DecisionTargetType;
|
|
156
|
+
target_id: string;
|
|
157
|
+
decision: DecisionValue;
|
|
158
|
+
reason?: string;
|
|
159
|
+
created_at: string;
|
|
160
|
+
applies_to_future_cycles: boolean;
|
|
161
|
+
}
|
|
162
|
+
export interface EvidenceReference {
|
|
163
|
+
evidence_id: string;
|
|
164
|
+
claim: string;
|
|
165
|
+
source_type: EvidenceSourceType;
|
|
166
|
+
source_path_or_url: string;
|
|
167
|
+
verified_at: string;
|
|
168
|
+
notes?: string;
|
|
169
|
+
stale?: boolean;
|
|
170
|
+
}
|
|
171
|
+
export interface EvidenceCatalog {
|
|
172
|
+
items: EvidenceReference[];
|
|
173
|
+
}
|
|
174
|
+
export interface CampaignStatusSummary {
|
|
175
|
+
cycle_id?: string;
|
|
176
|
+
status: CampaignStatus | "not_initialized";
|
|
177
|
+
profile_score: number;
|
|
178
|
+
content_coverage_days: number;
|
|
179
|
+
top_opportunity_id?: string;
|
|
180
|
+
next_action: string;
|
|
181
|
+
top_blocker?: string;
|
|
182
|
+
counts: {
|
|
183
|
+
opportunities: number;
|
|
184
|
+
content_items: number;
|
|
185
|
+
profile_recommendations: number;
|
|
186
|
+
pending_decisions: number;
|
|
187
|
+
};
|
|
188
|
+
}
|
|
189
|
+
export declare const DEFAULT_POSITIONING_PHRASES: string[];
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"models.js","sourceRoot":"","sources":["../../../src/marketing/models.ts"],"names":[],"mappings":"AAgOA,MAAM,CAAC,MAAM,2BAA2B,GAAG;IACzC,wBAAwB;IACxB,+BAA+B;CAChC,CAAC"}
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
import type { CampaignWorkspace, MarketingOpportunity, OpportunityBrief, TrendSnapshot } from "./models.js";
|
|
2
|
+
export interface OpportunityScoutResult {
|
|
3
|
+
opportunities: MarketingOpportunity[];
|
|
4
|
+
briefs: OpportunityBrief[];
|
|
5
|
+
}
|
|
6
|
+
export declare function runOpportunityScout(snapshot: TrendSnapshot, workspace: CampaignWorkspace, options?: {
|
|
7
|
+
now?: string;
|
|
8
|
+
}): OpportunityScoutResult;
|
|
@@ -0,0 +1,73 @@
|
|
|
1
|
+
import { computeOpportunityScore, opportunityTopic } from "./scoring.js";
|
|
2
|
+
import { assertTrendSnapshot, looksPrivateOrConfidential, textMatchesBlockedTopic } from "./validation.js";
|
|
3
|
+
export function runOpportunityScout(snapshot, workspace, options = {}) {
|
|
4
|
+
assertTrendSnapshot(snapshot);
|
|
5
|
+
const opportunities = [];
|
|
6
|
+
const briefs = [];
|
|
7
|
+
const now = options.now ?? new Date().toISOString();
|
|
8
|
+
snapshot.items.forEach((item, index) => {
|
|
9
|
+
const score = computeOpportunityScore(item, now);
|
|
10
|
+
const blockers = [];
|
|
11
|
+
const text = `${item.text} ${item.source_context}`;
|
|
12
|
+
const blockedTopic = textMatchesBlockedTopic(workspace, text);
|
|
13
|
+
if (blockedTopic)
|
|
14
|
+
blockers.push(`matches blocked topic: ${blockedTopic}`);
|
|
15
|
+
if (looksPrivateOrConfidential(text))
|
|
16
|
+
blockers.push("contains private or confidential information");
|
|
17
|
+
if (score.sverklo_relevance === "low")
|
|
18
|
+
blockers.push("low Sverklo relevance");
|
|
19
|
+
if (score.audience_fit === "low")
|
|
20
|
+
blockers.push("low audience fit");
|
|
21
|
+
if (score.urgency === "low")
|
|
22
|
+
blockers.push("stale or low-urgency topic");
|
|
23
|
+
let status = "briefed";
|
|
24
|
+
if (blockers.length > 0)
|
|
25
|
+
status = "rejected";
|
|
26
|
+
else if (score.brand_safety_risk === "high" || score.brand_safety_risk === "medium")
|
|
27
|
+
status = "needs_review";
|
|
28
|
+
const opportunityId = `opp-${String(index + 1).padStart(3, "0")}`;
|
|
29
|
+
const angle = recommendedAngle(item.text, workspace.positioning_phrases[0] ?? "local-first code intel");
|
|
30
|
+
opportunities.push({
|
|
31
|
+
opportunity_id: opportunityId,
|
|
32
|
+
source_item_id: item.id,
|
|
33
|
+
topic: opportunityTopic(item.text) || item.id,
|
|
34
|
+
source_context: item.source_context,
|
|
35
|
+
audience_fit: score.audience_fit,
|
|
36
|
+
sverklo_relevance: score.sverklo_relevance,
|
|
37
|
+
urgency: score.urgency,
|
|
38
|
+
novelty: score.novelty,
|
|
39
|
+
credibility_impact: score.credibility_impact,
|
|
40
|
+
brand_safety_risk: score.brand_safety_risk,
|
|
41
|
+
score: score.total,
|
|
42
|
+
recommended_angle: angle,
|
|
43
|
+
status,
|
|
44
|
+
blockers,
|
|
45
|
+
rationale: score.reasons,
|
|
46
|
+
});
|
|
47
|
+
if (status !== "rejected") {
|
|
48
|
+
briefs.push({
|
|
49
|
+
brief_id: `brief-${String(briefs.length + 1).padStart(3, "0")}`,
|
|
50
|
+
opportunity_id: opportunityId,
|
|
51
|
+
summary: item.text,
|
|
52
|
+
why_now: score.urgency === "high" ? "Current conversation is active now." : "Relevant during this campaign cycle.",
|
|
53
|
+
target_audience: score.audience_fit === "high" ? "AI coding and developer-tool builders" : "Developer-tool audience",
|
|
54
|
+
message_angle: angle,
|
|
55
|
+
risk_flags: score.brand_safety_risk === "low" ? [] : [`${score.brand_safety_risk} brand-safety risk`],
|
|
56
|
+
approval_status: "pending",
|
|
57
|
+
});
|
|
58
|
+
}
|
|
59
|
+
});
|
|
60
|
+
opportunities.sort((a, b) => b.score - a.score || a.opportunity_id.localeCompare(b.opportunity_id));
|
|
61
|
+
return { opportunities, briefs };
|
|
62
|
+
}
|
|
63
|
+
function recommendedAngle(text, phrase) {
|
|
64
|
+
const lower = text.toLowerCase();
|
|
65
|
+
if (lower.includes("memory"))
|
|
66
|
+
return `Connect the conversation to ${phrase} and git-pinned decisions.`;
|
|
67
|
+
if (lower.includes("mcp"))
|
|
68
|
+
return `Show how ${phrase} improves MCP agent workflows.`;
|
|
69
|
+
if (lower.includes("local"))
|
|
70
|
+
return `Lead with the local-first privacy angle behind ${phrase}.`;
|
|
71
|
+
return `Add a terse technical Sverklo example around ${phrase}.`;
|
|
72
|
+
}
|
|
73
|
+
//# sourceMappingURL=opportunity-scout.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"opportunity-scout.js","sourceRoot":"","sources":["../../../src/marketing/opportunity-scout.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,2 @@
|
|
|
1
|
+
import type { CampaignCycle, ProfileHealthReport, ProfileSnapshot, RecentPostsSnapshot } from "./models.js";
|
|
2
|
+
export declare function evaluateProfileHealth(profile: ProfileSnapshot, recentPosts: RecentPostsSnapshot | undefined, cycle: Pick<CampaignCycle, "cycle_id" | "content_queue">, now?: string): ProfileHealthReport;
|
|
@@ -0,0 +1,96 @@
|
|
|
1
|
+
import { assertProfileSnapshot, assertRecentPostsSnapshot } from "./validation.js";
|
|
2
|
+
export function evaluateProfileHealth(profile, recentPosts, cycle, now = new Date().toISOString()) {
|
|
3
|
+
assertProfileSnapshot(profile);
|
|
4
|
+
if (recentPosts)
|
|
5
|
+
assertRecentPostsSnapshot(recentPosts);
|
|
6
|
+
const clarityScore = scoreClarity(profile);
|
|
7
|
+
const credibilityScore = scoreCredibility(profile);
|
|
8
|
+
const alignmentScore = scoreAlignment(profile, cycle);
|
|
9
|
+
const cadenceScore = scoreCadence(recentPosts, now);
|
|
10
|
+
const score = Math.round((clarityScore + credibilityScore + alignmentScore + cadenceScore) / 4);
|
|
11
|
+
const reportId = `profile-${cycle.cycle_id}`;
|
|
12
|
+
const criticalGaps = [];
|
|
13
|
+
const recommendations = [];
|
|
14
|
+
addRecommendation(recommendations, reportId, "bio", clarityScore, "Clarify bio around local-first code intelligence.", "Visitors understand what Sverklo does in one scan.");
|
|
15
|
+
addRecommendation(recommendations, reportId, "credibility_signal", credibilityScore, "Add a concrete proof point or benchmark-backed claim.", "Visitors see why the account is worth trusting.");
|
|
16
|
+
addRecommendation(recommendations, reportId, "pinned_content", alignmentScore, "Refresh pinned content to match active campaign themes.", "Campaign visitors land on a relevant proof point.");
|
|
17
|
+
addRecommendation(recommendations, reportId, "cadence", cadenceScore, "Restore a visible posting cadence before launch.", "The account looks active and maintained.");
|
|
18
|
+
if (clarityScore < 70)
|
|
19
|
+
criticalGaps.push("unclear positioning in bio");
|
|
20
|
+
if (credibilityScore < 70)
|
|
21
|
+
criticalGaps.push("missing credibility signal");
|
|
22
|
+
if (alignmentScore < 70)
|
|
23
|
+
criticalGaps.push("profile theme mismatch");
|
|
24
|
+
if (cadenceScore < 50)
|
|
25
|
+
criticalGaps.push("posting cadence is stale");
|
|
26
|
+
return {
|
|
27
|
+
report_id: reportId,
|
|
28
|
+
cycle_id: cycle.cycle_id,
|
|
29
|
+
score,
|
|
30
|
+
clarity_score: clarityScore,
|
|
31
|
+
credibility_score: credibilityScore,
|
|
32
|
+
alignment_score: alignmentScore,
|
|
33
|
+
cadence_score: cadenceScore,
|
|
34
|
+
recommendations,
|
|
35
|
+
critical_gaps: criticalGaps,
|
|
36
|
+
};
|
|
37
|
+
}
|
|
38
|
+
function scoreClarity(profile) {
|
|
39
|
+
const text = `${profile.display_name} ${profile.bio}`.toLowerCase();
|
|
40
|
+
let score = 40;
|
|
41
|
+
if (text.includes("sverklo"))
|
|
42
|
+
score += 20;
|
|
43
|
+
if (text.includes("code") || text.includes("repo"))
|
|
44
|
+
score += 20;
|
|
45
|
+
if (text.includes("agent") || text.includes("mcp"))
|
|
46
|
+
score += 10;
|
|
47
|
+
if (text.includes("local"))
|
|
48
|
+
score += 10;
|
|
49
|
+
return Math.min(100, score);
|
|
50
|
+
}
|
|
51
|
+
function scoreCredibility(profile) {
|
|
52
|
+
let score = 45;
|
|
53
|
+
const text = `${profile.bio} ${profile.pinned_post ?? ""}`.toLowerCase();
|
|
54
|
+
if (profile.pinned_post?.trim())
|
|
55
|
+
score += 20;
|
|
56
|
+
if (text.includes("benchmark") || text.includes("local-first") || text.includes("open-source") || text.includes("mit"))
|
|
57
|
+
score += 25;
|
|
58
|
+
if (profile.profile_link?.startsWith("http"))
|
|
59
|
+
score += 10;
|
|
60
|
+
return Math.min(100, score);
|
|
61
|
+
}
|
|
62
|
+
function scoreAlignment(profile, cycle) {
|
|
63
|
+
const themes = cycle.content_queue?.items.map((item) => item.theme.toLowerCase()) ?? [];
|
|
64
|
+
const profileText = `${profile.bio} ${profile.pinned_post ?? ""}`.toLowerCase();
|
|
65
|
+
if (themes.length === 0)
|
|
66
|
+
return profileText.includes("sverklo") ? 80 : 50;
|
|
67
|
+
const matches = themes.filter((theme) => profileText.includes(theme) || theme.split(/\s+/).some((part) => profileText.includes(part)));
|
|
68
|
+
return Math.min(100, 50 + matches.length * 15);
|
|
69
|
+
}
|
|
70
|
+
function scoreCadence(recentPosts, now) {
|
|
71
|
+
if (!recentPosts || recentPosts.posts.length === 0)
|
|
72
|
+
return 20;
|
|
73
|
+
const newestAgeDays = Math.min(...recentPosts.posts.map((post) => Math.max(0, Date.parse(now) - Date.parse(post.posted_at)) / 864e5));
|
|
74
|
+
let score = recentPosts.posts.length >= 3 ? 60 : 45;
|
|
75
|
+
if (newestAgeDays <= 7)
|
|
76
|
+
score += 30;
|
|
77
|
+
else if (newestAgeDays <= 14)
|
|
78
|
+
score += 15;
|
|
79
|
+
return Math.min(100, score);
|
|
80
|
+
}
|
|
81
|
+
function addRecommendation(recommendations, reportId, area, score, recommendation, impact) {
|
|
82
|
+
if (score >= 85)
|
|
83
|
+
return;
|
|
84
|
+
const priority = score < 50 ? "critical" : score < 70 ? "high" : "medium";
|
|
85
|
+
recommendations.push({
|
|
86
|
+
recommendation_id: `profile-rec-${String(recommendations.length + 1).padStart(3, "0")}`,
|
|
87
|
+
report_id: reportId,
|
|
88
|
+
profile_area: area,
|
|
89
|
+
recommendation,
|
|
90
|
+
reason: `${area} score is ${score}`,
|
|
91
|
+
expected_visitor_impact: impact,
|
|
92
|
+
priority,
|
|
93
|
+
status: "proposed",
|
|
94
|
+
});
|
|
95
|
+
}
|
|
96
|
+
//# sourceMappingURL=profile-health.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"profile-health.js","sourceRoot":"","sources":["../../../src/marketing/profile-health.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,4 @@
|
|
|
1
|
+
import type { CampaignCycle } from "./models.js";
|
|
2
|
+
export declare function renderOpportunityReport(cycle: CampaignCycle): string;
|
|
3
|
+
export declare function renderContentReport(cycle: CampaignCycle): string;
|
|
4
|
+
export declare function renderProfileHealthReport(cycle: CampaignCycle): string;
|
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
export function renderOpportunityReport(cycle) {
|
|
2
|
+
const lines = [`# Opportunities: ${cycle.cycle_id}`, ""];
|
|
3
|
+
for (const opp of cycle.opportunities) {
|
|
4
|
+
lines.push(`## ${opp.opportunity_id}: ${opp.topic}`);
|
|
5
|
+
lines.push("");
|
|
6
|
+
lines.push(`- Status: ${opp.status}`);
|
|
7
|
+
lines.push(`- Score: ${opp.score}`);
|
|
8
|
+
lines.push(`- Audience fit: ${opp.audience_fit}`);
|
|
9
|
+
lines.push(`- Sverklo relevance: ${opp.sverklo_relevance}`);
|
|
10
|
+
lines.push(`- Urgency: ${opp.urgency}`);
|
|
11
|
+
lines.push(`- Brand risk: ${opp.brand_safety_risk}`);
|
|
12
|
+
lines.push(`- Angle: ${opp.recommended_angle}`);
|
|
13
|
+
if (opp.blockers.length > 0)
|
|
14
|
+
lines.push(`- Blockers: ${opp.blockers.join("; ")}`);
|
|
15
|
+
lines.push("");
|
|
16
|
+
}
|
|
17
|
+
return lines.join("\n");
|
|
18
|
+
}
|
|
19
|
+
export function renderContentReport(cycle) {
|
|
20
|
+
const lines = [`# Content Queue: ${cycle.cycle_id}`, ""];
|
|
21
|
+
const queue = cycle.content_queue;
|
|
22
|
+
if (!queue)
|
|
23
|
+
return `${lines.join("\n")}No content queue generated.\n`;
|
|
24
|
+
lines.push(`Coverage days: ${queue.coverage_days}`);
|
|
25
|
+
if (queue.gaps.length > 0)
|
|
26
|
+
lines.push(`Gaps: ${queue.gaps.join("; ")}`);
|
|
27
|
+
lines.push("");
|
|
28
|
+
for (const item of queue.items) {
|
|
29
|
+
lines.push(`## ${item.content_id}: ${item.theme}`);
|
|
30
|
+
lines.push("");
|
|
31
|
+
lines.push(`- Approval: ${item.approval_status}`);
|
|
32
|
+
lines.push(`- Quality: ${item.quality_status}`);
|
|
33
|
+
lines.push(`- Audience: ${item.intended_audience}`);
|
|
34
|
+
lines.push(`- Goal: ${item.goal}`);
|
|
35
|
+
if (item.blockers.length > 0)
|
|
36
|
+
lines.push(`- Blockers: ${item.blockers.join("; ")}`);
|
|
37
|
+
lines.push("");
|
|
38
|
+
lines.push(item.text);
|
|
39
|
+
lines.push("");
|
|
40
|
+
}
|
|
41
|
+
return lines.join("\n");
|
|
42
|
+
}
|
|
43
|
+
export function renderProfileHealthReport(cycle) {
|
|
44
|
+
const report = cycle.profile_report;
|
|
45
|
+
if (!report)
|
|
46
|
+
return `# Profile Health: ${cycle.cycle_id}\n\nNo profile health report generated.\n`;
|
|
47
|
+
const lines = [`# Profile Health: ${cycle.cycle_id}`, ""];
|
|
48
|
+
lines.push(`Score: ${report.score}`);
|
|
49
|
+
lines.push(`Clarity: ${report.clarity_score}`);
|
|
50
|
+
lines.push(`Credibility: ${report.credibility_score}`);
|
|
51
|
+
lines.push(`Alignment: ${report.alignment_score}`);
|
|
52
|
+
lines.push(`Cadence: ${report.cadence_score}`);
|
|
53
|
+
if (report.critical_gaps.length > 0)
|
|
54
|
+
lines.push(`Critical gaps: ${report.critical_gaps.join("; ")}`);
|
|
55
|
+
lines.push("");
|
|
56
|
+
for (const rec of report.recommendations) {
|
|
57
|
+
lines.push(`## ${rec.recommendation_id}: ${rec.profile_area}`);
|
|
58
|
+
lines.push("");
|
|
59
|
+
lines.push(`- Priority: ${rec.priority}`);
|
|
60
|
+
lines.push(`- Status: ${rec.status}`);
|
|
61
|
+
lines.push(`- Recommendation: ${rec.recommendation}`);
|
|
62
|
+
lines.push(`- Visitor impact: ${rec.expected_visitor_impact}`);
|
|
63
|
+
lines.push("");
|
|
64
|
+
}
|
|
65
|
+
return lines.join("\n");
|
|
66
|
+
}
|
|
67
|
+
//# sourceMappingURL=report.js.map
|