@contractspec/module.lifecycle-core 1.56.1 → 1.58.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.
Files changed (34) hide show
  1. package/dist/__tests__/stage-scorer.test.d.ts +2 -0
  2. package/dist/__tests__/stage-scorer.test.d.ts.map +1 -0
  3. package/dist/adapters/analytics-adapter.d.ts +7 -11
  4. package/dist/adapters/analytics-adapter.d.ts.map +1 -1
  5. package/dist/adapters/intent-adapter.d.ts +5 -9
  6. package/dist/adapters/intent-adapter.d.ts.map +1 -1
  7. package/dist/adapters/questionnaire-adapter.d.ts +7 -11
  8. package/dist/adapters/questionnaire-adapter.d.ts.map +1 -1
  9. package/dist/browser/index.js +427 -0
  10. package/dist/collectors/signal-collector.d.ts +17 -21
  11. package/dist/collectors/signal-collector.d.ts.map +1 -1
  12. package/dist/index.d.ts +8 -8
  13. package/dist/index.d.ts.map +1 -0
  14. package/dist/index.js +427 -5
  15. package/dist/node/index.js +427 -0
  16. package/dist/orchestrator/lifecycle-orchestrator.d.ts +16 -19
  17. package/dist/orchestrator/lifecycle-orchestrator.d.ts.map +1 -1
  18. package/dist/planning/milestone-planner.d.ts +5 -9
  19. package/dist/planning/milestone-planner.d.ts.map +1 -1
  20. package/dist/scoring/stage-scorer.d.ts +18 -19
  21. package/dist/scoring/stage-scorer.d.ts.map +1 -1
  22. package/package.json +20 -15
  23. package/dist/collectors/signal-collector.js +0 -65
  24. package/dist/collectors/signal-collector.js.map +0 -1
  25. package/dist/data/milestones-catalog.js +0 -74
  26. package/dist/data/milestones-catalog.js.map +0 -1
  27. package/dist/data/stage-weights.js +0 -170
  28. package/dist/data/stage-weights.js.map +0 -1
  29. package/dist/orchestrator/lifecycle-orchestrator.js +0 -53
  30. package/dist/orchestrator/lifecycle-orchestrator.js.map +0 -1
  31. package/dist/planning/milestone-planner.js +0 -17
  32. package/dist/planning/milestone-planner.js.map +0 -1
  33. package/dist/scoring/stage-scorer.js +0 -64
  34. package/dist/scoring/stage-scorer.js.map +0 -1
package/dist/index.js CHANGED
@@ -1,6 +1,428 @@
1
- import { StageSignalCollector } from "./collectors/signal-collector.js";
2
- import { StageScorer } from "./scoring/stage-scorer.js";
3
- import { LifecycleMilestonePlanner } from "./planning/milestone-planner.js";
4
- import { LifecycleOrchestrator } from "./orchestrator/lifecycle-orchestrator.js";
1
+ // @bun
2
+ // src/collectors/signal-collector.ts
3
+ import {
4
+ CapitalPhase,
5
+ CompanyPhase,
6
+ ProductPhase
7
+ } from "@contractspec/lib.lifecycle";
8
+ var DEFAULT_AXES = {
9
+ product: ProductPhase.Sketch,
10
+ company: CompanyPhase.Solo,
11
+ capital: CapitalPhase.Bootstrapped
12
+ };
5
13
 
6
- export { LifecycleMilestonePlanner, LifecycleOrchestrator, StageScorer, StageSignalCollector };
14
+ class StageSignalCollector {
15
+ options;
16
+ constructor(options) {
17
+ this.options = options;
18
+ }
19
+ async collect(input = {}) {
20
+ const axes = {
21
+ ...DEFAULT_AXES,
22
+ ...input.axes ?? {}
23
+ };
24
+ const metricsSnapshots = [];
25
+ const aggregatedSignals = [...input.signals ?? []];
26
+ const questionnaireAnswers = {
27
+ ...input.questionnaireAnswers ?? {}
28
+ };
29
+ if (input.metrics) {
30
+ metricsSnapshots.push(input.metrics);
31
+ }
32
+ if (this.options.analyticsAdapter) {
33
+ const result = await this.options.analyticsAdapter.fetch();
34
+ if (result.axes)
35
+ Object.assign(axes, result.axes);
36
+ if (result.metrics)
37
+ metricsSnapshots.push(result.metrics);
38
+ if (result.signals)
39
+ aggregatedSignals.push(...result.signals);
40
+ }
41
+ if (this.options.questionnaireAdapter) {
42
+ const result = await this.options.questionnaireAdapter.fetch();
43
+ if (result.axes)
44
+ Object.assign(axes, result.axes);
45
+ if (result.signals)
46
+ aggregatedSignals.push(...result.signals);
47
+ Object.assign(questionnaireAnswers, result.answers);
48
+ }
49
+ if (this.options.intentAdapter) {
50
+ const result = await this.options.intentAdapter.fetch();
51
+ if (result.signals)
52
+ aggregatedSignals.push(...result.signals);
53
+ }
54
+ const metrics = mergeMetricSnapshots(metricsSnapshots);
55
+ return {
56
+ axes,
57
+ metrics,
58
+ signals: dedupeSignals(aggregatedSignals),
59
+ questionnaireAnswers: Object.keys(questionnaireAnswers).length ? questionnaireAnswers : undefined
60
+ };
61
+ }
62
+ }
63
+ var mergeMetricSnapshots = (snapshots) => snapshots.reduce((acc, snapshot) => {
64
+ Object.entries(snapshot ?? {}).forEach(([key, value]) => {
65
+ if (value !== undefined && value !== null) {
66
+ acc[key] = value;
67
+ }
68
+ });
69
+ return acc;
70
+ }, {});
71
+ var dedupeSignals = (signals) => {
72
+ const seen = new Set;
73
+ return signals.filter((signal) => {
74
+ if (!signal.id)
75
+ return true;
76
+ if (seen.has(signal.id))
77
+ return false;
78
+ seen.add(signal.id);
79
+ return true;
80
+ });
81
+ };
82
+ // src/scoring/stage-scorer.ts
83
+ import {
84
+ LIFECYCLE_STAGE_META,
85
+ LifecycleStage
86
+ } from "@contractspec/lib.lifecycle";
87
+ // src/data/stage-weights.json
88
+ var stage_weights_default = {
89
+ Exploration: {
90
+ base: 0.35,
91
+ metrics: {
92
+ activeUsers: { weight: 0.3, direction: "lte", threshold: 5 },
93
+ customerCount: { weight: 0.2, direction: "lte", threshold: 3 },
94
+ teamSize: { weight: 0.1, direction: "lte", threshold: 3 }
95
+ },
96
+ signalKinds: {
97
+ qualitative: 0.4,
98
+ metric: 0.1
99
+ }
100
+ },
101
+ ProblemSolutionFit: {
102
+ base: 0.4,
103
+ metrics: {
104
+ activeUsers: { weight: 0.2, direction: "gte", threshold: 5 },
105
+ customerCount: { weight: 0.2, direction: "gte", threshold: 3 }
106
+ },
107
+ signalKinds: {
108
+ qualitative: 0.3,
109
+ event: 0.1
110
+ }
111
+ },
112
+ MvpEarlyTraction: {
113
+ base: 0.45,
114
+ metrics: {
115
+ activeUsers: { weight: 0.25, direction: "gte", threshold: 25 },
116
+ weeklyActiveUsers: {
117
+ weight: 0.25,
118
+ direction: "gte",
119
+ threshold: 20
120
+ },
121
+ retentionRate: { weight: 0.2, direction: "gte", threshold: 0.25 }
122
+ },
123
+ signalKinds: {
124
+ metric: 0.15,
125
+ event: 0.1
126
+ }
127
+ },
128
+ ProductMarketFit: {
129
+ base: 0.5,
130
+ metrics: {
131
+ retentionRate: {
132
+ weight: 0.35,
133
+ direction: "gte",
134
+ threshold: 0.45
135
+ },
136
+ monthlyRecurringRevenue: {
137
+ weight: 0.25,
138
+ direction: "gte",
139
+ threshold: 1e4
140
+ },
141
+ customerCount: { weight: 0.2, direction: "gte", threshold: 30 }
142
+ },
143
+ signalKinds: {
144
+ metric: 0.15,
145
+ event: 0.1
146
+ }
147
+ },
148
+ GrowthScaleUp: {
149
+ base: 0.55,
150
+ metrics: {
151
+ retentionRate: { weight: 0.2, direction: "gte", threshold: 0.55 },
152
+ monthlyRecurringRevenue: {
153
+ weight: 0.3,
154
+ direction: "gte",
155
+ threshold: 50000
156
+ },
157
+ teamSize: { weight: 0.15, direction: "gte", threshold: 15 }
158
+ },
159
+ signalKinds: {
160
+ event: 0.2,
161
+ metric: 0.15
162
+ }
163
+ },
164
+ ExpansionPlatform: {
165
+ base: 0.6,
166
+ metrics: {
167
+ monthlyRecurringRevenue: {
168
+ weight: 0.35,
169
+ direction: "gte",
170
+ threshold: 150000
171
+ },
172
+ customerCount: { weight: 0.2, direction: "gte", threshold: 100 },
173
+ teamSize: { weight: 0.15, direction: "gte", threshold: 40 }
174
+ },
175
+ signalKinds: {
176
+ event: 0.2,
177
+ milestone: 0.1
178
+ }
179
+ },
180
+ MaturityRenewal: {
181
+ base: 0.6,
182
+ metrics: {
183
+ monthlyRecurringRevenue: {
184
+ weight: 0.25,
185
+ direction: "gte",
186
+ threshold: 250000
187
+ },
188
+ teamSize: { weight: 0.15, direction: "gte", threshold: 80 },
189
+ burnMultiple: { weight: 0.2, direction: "lte", threshold: 1.5 }
190
+ },
191
+ signalKinds: {
192
+ event: 0.2,
193
+ milestone: 0.15
194
+ }
195
+ }
196
+ };
197
+
198
+ // src/scoring/stage-scorer.ts
199
+ var DEFAULT_WEIGHTS = stage_weights_default;
200
+
201
+ class StageScorer {
202
+ weights;
203
+ constructor(weights = DEFAULT_WEIGHTS) {
204
+ this.weights = weights;
205
+ }
206
+ score(input) {
207
+ const kindStrength = evaluateSignalKinds(input.signals);
208
+ const scores = Object.values(LifecycleStage).filter(isStageValue).map((stage) => {
209
+ const stageName = LifecycleStage[stage];
210
+ const config = this.weights[stageName] ?? { base: 0.5 };
211
+ let score = config.base ?? 0.5;
212
+ let contributions = 0;
213
+ const totalPossible = Object.keys(config.metrics ?? {}).length + Object.keys(config.signalKinds ?? {}).length || 1;
214
+ const supportingSignals = [];
215
+ if (config.metrics) {
216
+ Object.entries(config.metrics).forEach(([metricKey, metricConfig]) => {
217
+ const value = input.metrics[metricKey];
218
+ if (value === undefined || value === null)
219
+ return;
220
+ if (passesThreshold(value, metricConfig)) {
221
+ score += metricConfig.weight;
222
+ contributions += 1;
223
+ supportingSignals.push(`metric:${metricKey}`);
224
+ } else {
225
+ score += metricConfig.weight * 0.25;
226
+ }
227
+ });
228
+ }
229
+ if (config.signalKinds) {
230
+ Object.entries(config.signalKinds).forEach(([kind, weight]) => {
231
+ const strength = kindStrength[kind] ?? 0;
232
+ if (strength > 0 && typeof weight === "number") {
233
+ score += weight;
234
+ contributions += 1;
235
+ supportingSignals.push(`signal:${kind}`);
236
+ }
237
+ });
238
+ }
239
+ score = clamp(score, 0, 1.25);
240
+ const confidence = clamp(contributions / totalPossible, 0.1, 1);
241
+ return {
242
+ stage,
243
+ score,
244
+ confidence,
245
+ supportingSignals
246
+ };
247
+ });
248
+ return scores.sort((a, b) => {
249
+ if (b.score === a.score) {
250
+ return b.confidence - a.confidence;
251
+ }
252
+ return b.score - a.score;
253
+ });
254
+ }
255
+ }
256
+ var passesThreshold = (value, config) => {
257
+ const direction = config.direction ?? "gte";
258
+ if (direction === "gte") {
259
+ return value >= config.threshold;
260
+ }
261
+ return value <= config.threshold;
262
+ };
263
+ var clamp = (value, min, max) => Math.min(Math.max(value, min), max);
264
+ var evaluateSignalKinds = (signals) => signals.reduce((acc, signal) => {
265
+ const key = signal.kind ?? "unknown";
266
+ acc[key] = (acc[key] ?? 0) + (signal.weight ?? 1);
267
+ return acc;
268
+ }, {});
269
+ var isStageValue = (value) => typeof value === "number" && (value in LIFECYCLE_STAGE_META);
270
+ // src/orchestrator/lifecycle-orchestrator.ts
271
+ import {
272
+ LIFECYCLE_STAGE_META as LIFECYCLE_STAGE_META2,
273
+ LifecycleStage as LifecycleStage2
274
+ } from "@contractspec/lib.lifecycle";
275
+
276
+ class LifecycleOrchestrator {
277
+ collector;
278
+ scorer;
279
+ planner;
280
+ constructor(options) {
281
+ this.collector = options.collector;
282
+ this.scorer = options.scorer;
283
+ this.planner = options.milestonePlanner;
284
+ }
285
+ async run(input) {
286
+ const collected = await this.collector.collect(input);
287
+ const scorecard = this.scorer.score({
288
+ metrics: collected.metrics,
289
+ signals: collected.signals
290
+ });
291
+ const top = scorecard[0] ?? fallbackScore();
292
+ const meta = LIFECYCLE_STAGE_META2[top.stage];
293
+ return {
294
+ stage: top.stage,
295
+ confidence: top.confidence,
296
+ axes: collected.axes,
297
+ signals: collected.signals,
298
+ metrics: toMetricRecord(collected.metrics),
299
+ gaps: meta.focusAreas.slice(0, 3),
300
+ focusAreas: meta.focusAreas,
301
+ scorecard,
302
+ generatedAt: new Date().toISOString()
303
+ };
304
+ }
305
+ getUpcomingMilestones(stage, completedMilestoneIds = [], limit = 5) {
306
+ if (!this.planner)
307
+ return [];
308
+ return this.planner.getUpcoming(stage, completedMilestoneIds, limit);
309
+ }
310
+ }
311
+ var fallbackScore = () => ({
312
+ stage: LifecycleStage2.Exploration,
313
+ score: 0.3,
314
+ confidence: 0.3,
315
+ supportingSignals: []
316
+ });
317
+ var toMetricRecord = (snapshot) => Object.entries(snapshot ?? {}).reduce((acc, [key, value]) => {
318
+ if (typeof value === "number") {
319
+ acc[key] = value;
320
+ }
321
+ return acc;
322
+ }, {});
323
+ // src/data/milestones-catalog.json
324
+ var milestones_catalog_default = [
325
+ {
326
+ id: "stage0-problem-statement",
327
+ stage: 0,
328
+ category: "product",
329
+ title: "Write the pain statement",
330
+ description: "Capture the clearest description of the top problem in the customer\u2019s own words.",
331
+ priority: 1,
332
+ actionItems: [
333
+ "Interview at least 5 ideal customers",
334
+ "Synthesize quotes into a one-page brief"
335
+ ]
336
+ },
337
+ {
338
+ id: "stage1-prototype-loop",
339
+ stage: 1,
340
+ category: "product",
341
+ title: "Prototype feedback loop",
342
+ description: "Ship a clickable prototype and gather 3 rounds of feedback.",
343
+ priority: 2,
344
+ actionItems: [
345
+ "Create a low-fidelity prototype",
346
+ "Schedule standing feedback calls"
347
+ ]
348
+ },
349
+ {
350
+ id: "stage2-activation",
351
+ stage: 2,
352
+ category: "operations",
353
+ title: "Activation checklist",
354
+ description: "Define the minimum steps required for a new user to succeed.",
355
+ priority: 1,
356
+ actionItems: [
357
+ "Document onboarding flow",
358
+ "Instrument activation analytics"
359
+ ]
360
+ },
361
+ {
362
+ id: "stage3-retention-narrative",
363
+ stage: 3,
364
+ category: "product",
365
+ title: "Retention narrative",
366
+ description: "Create the before/after story that proves why users stay.",
367
+ priority: 2,
368
+ actionItems: [
369
+ "Interview 3 retained users",
370
+ "Publish a one-pager with concrete metrics"
371
+ ]
372
+ },
373
+ {
374
+ id: "stage4-growth-loop",
375
+ stage: 4,
376
+ category: "growth",
377
+ title: "Install a growth loop",
378
+ description: "Stand up a repeatable acquisition \u2192 activation \u2192 referral motion.",
379
+ priority: 1,
380
+ actionItems: [
381
+ "Define loop metrics",
382
+ "Assign owners for each stage",
383
+ "Review weekly"
384
+ ]
385
+ },
386
+ {
387
+ id: "stage5-platform-blueprint",
388
+ stage: 5,
389
+ category: "product",
390
+ title: "Platform blueprint",
391
+ description: "Align on APIs, integrations, and governance for partners.",
392
+ priority: 2,
393
+ actionItems: [
394
+ "Create integration scoring rubric",
395
+ "Publish partner onboarding checklist"
396
+ ]
397
+ },
398
+ {
399
+ id: "stage6-renewal-ops",
400
+ stage: 6,
401
+ category: "operations",
402
+ title: "Renewal operating rhythm",
403
+ description: "Decide whether to optimize, reinvest, or sunset each major surface.",
404
+ priority: 1,
405
+ actionItems: [
406
+ "Hold quarterly renewal review",
407
+ "Document reinvestment bets"
408
+ ]
409
+ }
410
+ ];
411
+
412
+ // src/planning/milestone-planner.ts
413
+ class LifecycleMilestonePlanner {
414
+ milestones;
415
+ constructor(customCatalog) {
416
+ this.milestones = customCatalog ?? milestones_catalog_default;
417
+ }
418
+ getUpcoming(stage, completedIds = [], limit = 5) {
419
+ const completedSet = new Set(completedIds);
420
+ return this.milestones.filter((milestone) => milestone.stage === stage && !completedSet.has(milestone.id)).sort((a, b) => a.priority - b.priority).slice(0, limit);
421
+ }
422
+ }
423
+ export {
424
+ StageSignalCollector,
425
+ StageScorer,
426
+ LifecycleOrchestrator,
427
+ LifecycleMilestonePlanner
428
+ };